Эмуляция экрана Flipper Zero, эмулятор для эмулятора и новогодний подарок

Ура, я получил свой девкит Flipper Zero, теперь я полноценный разработчик! Выглядит это чудо примерно вот так:

И теперь я чувствую на себе большую ответственность, ведь на данный момент далеко не все могут похвастать возможностью отладки на реальном железе. Конечно, большинство компонентов можно купить на Aliexpress/Ebay, но это надо ждать целый месяц! Давайте попробуем сделать эмулятор, хотя бы экранчика! Ведь еще в прошлой статье (ссылка), была показана клавиатурка, которая была прифотошоплена к остальной фотке, выглядело это просто страшно. Мне до сих пор стыдно за ту картинку, потому давайте это исправим.


Идея

Надо взять реальный экранчик, поставить рядом с ним ULTRA-SUPER-DUPER-HD-видеокамеру, засветить каждый пиксель на экране Флиппера и вырезать его ножницами! Правда пикселей около 8к, резать руками будет долго. Тогда, можно написать программу для вырезания, сам экранчик снять это на видео, засвечивать каждый пиксель, сразу его вырезать, и потом использовать эти картинки в эмуляторе! Так как засветка пикселя может повлиять и на соседние пиксели, то это тоже надо учитывать. Благо, у нас все пиксели расположены в матричном порядке, а значит рядом с одним пикселем будет еще 8 соседних пикселей. Получаем формулу:

128х64х2^(8+1)=4194304 возможных состояний каждого пикселя

(спойлер)

Не надо это путать с числом всех возможных состояний экрана, что можно посчитать по другой формуле:

2^(128*64) =

10907481356194159294629842447337828624482641619962326924318327861897\

21331849119295216264234525201987223957291796157025273109870820177184\

06361097976507755479907890629884219298953860982522804820515969685161\

35916381967718865426093245601212905539018863010179002525357999172000\

10079600026535836800905297805880952350501630195475653911005312364560\

01484742603529355124584392891875276869627934408805561751569434994540\

66778251408149006161059202564385045780133264935658360472424073824428\

12245131517757519164899226365743722432277368075027627883045206501792\

76170094569916849725787968385173704999690096112051565505011556127149\

14925153421057489666295470327863215057308284302216649703243961386352\

51626409516168005427623435996308921691446181187406395310665404885739\

43483287742816740749537099351186875635997039011702182361674945862096\

98570062636120827067154081570665751372810270223109275649102767591605\

20878304632411049364568754920967322982459184763427383790272448438018\

52697776494107271561158043469082745933999196141424274141059911742606\

05564837637563145276113626586283833686211579936380208785376755453367\

89915694234433955666315070087213535470255670312004130725495834508357\

43965382893607708097855057891296790735278005493562156109079584517295\

41159729274798775277385600082041185589300047777487277618538135104938\

40581861598652211605960308356405941821189714037868726219481498727603\

65361629885617482241303348543878532402475141941718301228107820972930\

35373728045743720952287036227763639452908698062584223551485075710396\

19387449629866808188769662815778153079393179093143648340761738581819\

56300299442279075495506128881830843007964869323217915876591803556521\

61571154029921202761556078731079374774668415283629877086994501520312\

31862594203085693838944657061346236704234026821102958954951197087076\

54618662279629453645162075650935101890602377382153953277620867697858\

97319663303088933046651694361850783506415683369445300514374913112988\

34367265238595404904273455928723949525227184617404367854754610474377\

01976802557660588103807727070771794222197709038543858584409549211609\

98525389039746557039439730860909305969633607675299649384145981857059\

63754561497355827813623833288906309004288017321424808663962671333528\

00923275835087305961411872378142210146019861574738685509689608918918\

04413395585248228675411132126387936755676503403629700319300233978284\

65318547238244232028015189689660418822976000815437610652254270163595\

65087543385114712321422726660540358178146909080657646895058766199718\

6505665475715792896 всех возможных состояний экрана

(/спойлер)

Ну, значит надо нарезать 4194304 картинок! Приступим!

Реализация

И тут как всегда, проблема. То нету реального девайса, то... Нету хорошей камеры, на которую это можно было бы снять! ULTRA-SUPER-DUPER-HD-видеокамеры у меня пока нет, так что... Пойду вздремн^W^WПРИШЛО НАПИСАТЬ ЭМУЛЯТОР ДЛЯ НАПИСАНИЯ ЭМУЛЯТОРА!!

Эмулятор для эмулятора

Немного безумных умений и программирования на HTML и мы получаем:


Если кто-то хочет поиграть с этим, то вот ссылка на исходник. Здесь 3D-трансформация используется для того, чтобы изобразить наклон видеокамеры, свечение вокруг пикселя призвано изобразить "засветку" и возможное смешивание пикселей на видеокамере, а разные цвета сделаны для того, чтобы нагляднее видеть "смешивание" пикселей на их стыках. Так что не надо думать, что я принял какие-то не те таблетки и экранчик на Флиппере стал цветным - все это сделано для большей наглядности на этапе отладки. Тем более, что если речь идет о однотонных текстурках, то можно легко ошибиться, так что такой разноцветный "эмулятор" все равно надо было бы делать, чтобы точно не допустить ошибок при обработке реального экрана.

Я не удержался и нарисовал маленькую демку:


И сразу забежал немного вперед, добавив энертность. Эта энертность сделана "на глаз", она совершенно неправильная, но тем не менее:


К моему удивлению, получилось очень даже красиво, а текст вполне читаем.

Обратите внимание, что несмотря на всю свою цветастость, этот экранчик 1-битный, т.е. цвета не сдвигаются. Чем-то это напоминает желто-синие экранчики SSD1306, где сверху полоска пикселей одного цвета, потом полоска "черных" пикселей, которые нельзя включить, а потом идут пиксели другого цвета.

(спойлер)

К слову, эта симуляция экрана у меня лютейше тормозит на моей машине, потому я был вынужден поставить анимацию в 0.5 FPS и смотрел на это по большей части в статике. Текст превращался в кашу и был совершенно нечитаем. Но когда я вырезал одинаковые кадры, то я был крайне приятно удивлен получившейся анимацией и читабельности текста.

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

# cat filter-frames.c

#include

#include

#include

#include

#define WIDTH 640

#define HEIGHT 480

#define LUMASIZE (WIDTH*HEIGHT)

#define FRAMESIZE (LUMASIZE+LUMASIZE/2)

int main(){

uint8_t *prev=malloc(FRAMESIZE);

uint8_t *frame=malloc(FRAMESIZE);

int q,is_same,same_frames=0;

while(1){

if(fread(frame,1,FRAMESIZE,stdin)<=0){break;}

is_same=1;

for(q=0;qif(abs(frame[q]-prev[q])>0x80){

is_same=0;break;

}

}

if(is_same){

same_frames++;

} else {

if(same_frames>10){

fwrite(prev,1,FRAMESIZE,stdout);

}

memcpy(prev,frame,FRAMESIZE);

same_frames=0;

}

}

return 0;

}

Сначала компиляем:

gcc filter-frames.c

Потом запускаем:

ffmpeg -i captured_video.mp4 -f rawvideo -

./a.out

ffmpeg -s 640x480 -pix_fmt yuv420p -f rawvideo -i - -y filtered.mp4

(предполагается, что исходное видео уже в 640х480, формат пикселя в yuv420p)

Таким образом можно легко и просто писать свои фильтры для видео, но не заморачиваться со сложными фреймворками

(/спойлер)

Итак, наш виртуально-реальный девайс готов, можно приступать к его эмулированию.

Если кто не понял - мы создали "виртуальный" экран, с эффектом засветки пикселей и "неизвестным" временем отклика, почти как любой другой реальный экранчик. Теперь самое время его эмулировать!

Нарезка данных

Первым делом я пустил линии по экрану, чтобы получить координаты реальных пикселей:


Затем я взял только чистые кадры и попробовал сделать из них маски... Но какой порог выставлять?



Как я не старался, но у меня не получалось получить чистые линии. Пришлось писать еще один фильтр на сишке и вычитать из кадра "фон", т.е. состояние, когда все пиксели выключены, получилось очень красиво и ровно:


[spoiler]

Здесь виден еще один косяк моего "эмулятора" - в то время как "столбцы" имеют выпуклости, "строки" здесь совершенно линейные, что выявляет дефект верстки. Впрочем, для нашего маленького исследования это практически не имеет никакого значения

[/spoiler]

Этой информации достаточно, чтобы разбить наш экранчик на "спрайты". Пока что это будут только лишь "знакоместа". Просто загружаем все фреймы в память (включая первый, где ничего нет, так как самый первый столбец "уполз" за экран), считаем первые 64 кадра за столбцы, остальные 16 кадров строками, а затем ищем пересечения. Если какой-то пиксель уже участвовал в пересечении, то его не считаем, так как он относится к уже найденному ранее "пикселю". Чтобы было как-то поинтереснее, проиндексируем все в рандомном порядке. Получается такая вот карта:


Тааак, надо замазать дырки! А это что тут у нас?


Да, я умышленно сделал рандомное наложение, но не мог и подумать, что будет такая вариативность в размерах. Это никуда не годится! Давайте посмотрим, а где эти "пиксели" пересекаются?


И опять виден косяк моего HTML-программирования - строчки совсем не смешиваются, зато столбцы - очень даже. Надо бы как-то заполнить эти черные пересечения значениями обоих соседних "пикселей". В идеале, надо бы изобрести какое-то подобие дизеринга или эрозии, но у меня тут за спиной стоят люди с вилами, которые просят уже дописать драйвер. Нельзя подводить команду, потому я использовал самый простой вариант заполнения - рандом, т.е. беру произвольную, но не занятую точку, рядом с ней еще одну точку, и если попал на "пиксель", то копирую ID этого пикселя. Повторить 100000000 раз:


WOW! Я сам такого не ожидал! Жаль только, что разброс пикселей слишком велик и там где наши виртуальные пиксели небольшие, некоторые из них перепрыгивают друг через друга. Пришлось уменьшить разброс:


Здесь уже нету перескоков, но и эффекта дизеринга тоже почти нет, по сути это и есть стохастическая эрозия. Можно было бы взять кисточку в Gimp и скопировать дизеринг из левой части, не задевая правую, до кучи еще обрезать "подтеки" по сторонам, чтобы не жрали память и ресурсы при копировании, но сейчас это не актуально, главное - сделать систему, которая позволит повторить все это с реальным экранчиком Флиппера.

Теперь, когда мы знаем координаты наших "пикселей" и их маски, пришло время... Позажигать эти пиксели на "реальном" железе:


Здесь мы не просто зажигаем каждый пиксель, но и все возможные варианты окружающих пикселей, чтобы наша маска, которая слегка наезжает на соседние пиксели, была всегда актуальна. Таким образом, на 1 пиксель будет записано 512 состояний (текстурок). С учетом энертности экрана, на это может уйти не один час, ведь надо выждать некоторое время, пока пиксели полностью не зажгутся/потухнут! У меня на рендеринг этого чуда ушло 1.5 часа! Чтобы все это было побыстрее, я решил использовать матрицу 4х4 (3х3 с учетом соседних пикселей + 1 пиксель в качестве отступа) и отрисовывать в этом же кадре и другие пиксели. На выходе должно получится 64х16х512=524288 текстурок (в случае с реальным Флиппером, у которого разрешение экрана 128х64, получим 128х64х512=4194304 текстурок). А как вы думали, как выглядит эта ваша модная Big Data?


Если кто не понял, то будем нарезать наше видео с "тетрисами" примерно по такой вот схеме. Точками обозначены центры. При реализации выяснилось, что самый маленький квадратик, который мы собираемся нарезать, у нас займет 10х10 пикселей. Не то, что это слишком много, но это займет 1 303 144 448 байт, что немного многовато для меня. И это только для тестового экранчика в 64х16! Так что пожалуй, я еще изображу из себя PS1, где текстурки хранились в 15-битном цвете. В дальнейшем я могу это еще оптимизировать: использовать палитру из 256 цветов, или же хранить строки спрайтов независимо, наверняка среди них будет много повторов. В крайнем случае, можно взять LZW или что-то такое.

[spoiler]

Всегда выводите в консольку отладку, даже если кажется, что задача тривиальна. У меня вот получились такие вот маски:


Это маска курильщика. Ну ладно, вертикальные и горизонтальные пробелы - это я не убрал отладочные линии, а что за мусор справа? А это я ошибся с указателями!


А вот так уже выглядит маска здорового человека! Представляю, сколько бы я это искал потом.

[/spoiler]

Энертность экрана

Теперь, переходим к самому интересному, тому, ради чего все затевалось. Благодаря энертности экранчика, можно попробовать получить полутона, которых контроллер дисплея не поддерживает сам, но которые возникают на нем во время работы. Если отрисовывать элементы последовательно, то на экране могут появляться красивые градиенты. Если вы игрались с такими экранами, то наверняка могли видеть в стандартных тестах Arduino, как отрисовывая увеличивающийся круг, на экране возникает красивая жирная точка с очень гладкими краями. В общем, надо записать энертность каждого "пикселя", это сделаем при помощи вот такой анимации:


Здесь я уже не ждал, пока загорится или потухнет каждый пиксель, потому мы можем видеть некоторое подобие градиента. Из этого видео мы тоже нарежем спрайты, но резать будем уже без учета состояний соседей, так как это было бы нереально большое количество. Остановитесь и подумайте, все ли тут верно? Достаточно ли корректно это утверждение?

Вообще, изначально я предполагал получить некий уровень "прозрачности" между включенным пикселем и потухшим, и сохранять уже это значение. Но как такую "прозрачность" проигрывать быстро? Аппаратный альфаблендинг - штука хорошая, но я хочу, чтобы эмулятор работал на любых машинах, даже старых. Таких, как моя. Сделать premultiplied alpha? Старый трюк, который освобождает нас от нескольких умножений. А так как количество состояний пикселя у нас конечное, то может быть тогда сразу нагенерить все возможные варианты? Хм... А зачем генерить, если можно воспользоваться записанным видео? В общем, я увлекся преждевременной оптимизацией!

С другой стороны, если посчитать прозрачность, то можно процесс зажигания/гашения превратить в некий график (кривую) и более красиво интерполировать. Кроме того, игры с регистрами контроллера экрана могут повлиять на такой график и быть может, в будущем, такой эмулятор, построенный на кривых затухания, сможет корректно эмулировать различные состояния внутри контроллера. Что-то в этом есть... Но что?

Я предполагаю, что энертность будет длиться не более 1 секунды, что при обновлении экрана в 30Hz, потребует не более 30 спрайтов. Ну, округлим их, пусть будет 32 состояния на зажигание и затухание, итого: 64х16х(32*2)=65536 текстурок во втором наборе, предназначенном исключительно для эмуляции энертности пикселей. А чтобы было удобнее работать с анимациями, надо как-то сравнивать состояние "пикселя" с полностью включенным или потушенным, чтобы зря не гонять анимации простых пикселей и использовать пиксели с учетом соседей. Значит, нужен какой-то маппинг.

Дожидаемся, пока пиксель полностью потухнет или зажгется, сравнивая с эталонным скриншотом, где все пиксели включены или погашены. Это будет точкой отсчета. При съемке реального видео, я наверное буду включать пищалку прямо в момент DMA-отправки фреймбуфера, чтобы использовать звук для синхронизации. Но так как написание этой статьи затянулось, можно поступить проще: поставить анимацию на паузу и посчитать руками количество пикселей в каждом состоянии перехода... В меня уже кидают яйца за затягивание сроков, потому так и сделаю. Это будет 2 и 7 - надо 2 кадра на включение и 7 на гашение, пока хардкодим эти значения. Потом смотрим, как пиксель менялся со временем и записываем эти значения. На время анимации, будут небольшие артефакты по краям, но так как анимация достаточно быстрая, я не думаю, что это будет сильно заметно.

Все ли тут корректно? Может быть я где-то ошибся в своих рассуждениях?

Ошибка проектирования

Да, ошибка тут есть. И как всегда, она нашлась уже в самый неудобный момент, когда эмулятор уже был почти написан, и оставалось реализовать лишь функцию анимации включения или гашения пикселя. Функция тривиальна, особенно потому, что в нашем случае лишь зависит от времени. Просто прибавляем единичку и... А что будет, если пиксель еще полностью не зажегся, а уже был потушен? В моем тестовом "эмуляторе" время зажигания и гашения - не равное. А что будет, если пиксель постоянно дергать включением и выключением, раз 50 к ряду? Будет ли накапливаться некоторая ошибка, которая будет обеспечивать дрифт, или же светимость будет сохраняться в зависимости от состояния пикселя, при котором началось это дерганье? Как на это будут влиять внутренние регистры контроллера экрана, которые можно менять во время работы?

Но так или иначе, сдавать проект мне все равно надо. Выкидывать ли написанное, вместе с этой статьей, или дать ее миру, чтобы он увидел мою ошибку и стал немного умнее? Ладно, буду делать, как есть... Тем более, что теперь и другим будет над чем поработать, если будет свободное время. Быть может, кто-то сделает более красивый и точный эмулятор, на сей раз не повторяя моих ошибок.

К тому же, при реализации выяснилось, что я видимо некорректно распарсил захваченное видео


Ну, буду считать это еще одним уроком. Значит, при реализации следующей версии, надо предусмотреть инструмент, рисующий "время" и позволяющий выявлять такие вот некорректные маппинги. Ну и конечно же, ДЕРЖАТЬ КАМЕРУ РОВНО, чтобы избежать таких перепадов. Пока пошаманю с волшебными цифрами, чтобы ужаса было поменьше. Эх, ведь когда-то я хотел стать QA, но так и не понял, чем они занимаются.

Формат

Теперь самое время изобрести какой-то файловый формат, куда все это сложить. У нас есть размеры текстурок и их положение, 2 набора битмапов, с учетом соседей и с учетом затухания. Так как маски всегда одинаковы, то имело бы смысл вынести их в отдельный массивчик... Но работать с 32-х битными числами УДОБНЕЕ. Впрочем, это раздувает объем памяти и ранее уже пришлось обрезать цвета до 15 бит + 1 бит на альфу.

* (x,y,width,height) - по 16 бит на значение, для каждого "пикселя" - 1024 таких структурки

* потом 16-битные битмапы, по 512 текстурок на эмулируемый "пиксель", положение текстурки определяется состоянием соседних "пикселей".

В отдельном файлике положим другие текстурки:

* 2 и 7 текстурок для включения и гашения каждого "пикселя", нарезанные ранее

Если кому-то нужны картинки текстур в виде PNG, то я попробую сделать для вас архивчик, просто напишите.

Эмулятор

Теперь у нас все готово к эмуляции картинки. И делать мы это можем крайне быстро, ведь ни софтварный, ни аппаратный альфаблендинг нам использовать не нужно. Используем самое простое копирование данных! Ну, немного подвигать биты придется, так как битмапы у нас в 15 битах, а на выходе надо отдать 32 бита. Берем SDL, создаем окошко...

Так как самый левый столбец у меня убежал за границу экрана, то пикселей в нем не видно, а значит я не смогу корректно отрисовать и соседний столбец. Придется вставить костыль, выключающий пиксели в 2х левых рядах.


Что мы тут видим?

* Рваные края, которых в теории, мы не должны видеть. Такие края есть у каждого "пикселя", но видны они только с краю из-за костыля выше. Исправится нормальным захватом видео. Еще можно сделать рескейл результирующей картинки в 4 раза.

* Артефакты компрессии, надо бы не просто хватать первые попавшиеся кадры, но еще и суммировать похожие. У меня правда нету столько оперативки, так что наверное буду смешивать просто несколько кадров.

* На дальнем плане вообще много мусора, а при анимации еще видно, как ломаются последовательности зажигания пикселей. Видимо, камеру надо будет держать все же максимально ровно, чтобы этого не допустить.

А в целом, очень даже неплохо! Давайте теперь добавим плавное включение/затухание:


В принципе, очень даже похоже. Я рад, что это получилось.

Скачать это безобразие можно здесь: supervirus.exe.7z

Альтернативный путь

Многие могут спросить: зачем такие сложности? Если уж так сильно хочется, то достаточно снять каждый пиксель отдельно, вырезать его, а потом уже аккуратно переносить во фреймбуфер, можно хоть с альфаблендингом, хоть без него. Ключевой момент здесь - вырезать каждый пиксель. И если вырезать в пространстве, относильно других пикселей, сложная, но еще решаемая задача, то как пиксель отрезать от "фона" засветки? Поразмышляв над этим вопросом, я пришел к тому, что было описано в этой статье. Впрочем, когда будут реальные записи экранчика с хорошей видеокамеры, я еще поэкспериментирую, быть может, найдется еще какой-то путь. А если вы его уже знаете, то подскажите!

Новогодний подарок

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

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

К примеру, можно сделать видеостену из шариков для настольного тенниса:

https://www.youtube.com/watch?v=EZEMK-C-nSo

https://www.youtube.com/watch?v=xFh8uiw7UiY

Эти видео с канала Bitluni's Lab, где очень трудолюбивый немец рассказывает о своих экспериментах с шариками и пишет различную демосцену на ESP32. Если вам что-то такое интересно, то вам обязательно стоит посмотреть видео с его канала. Или хотя бы увидеть, как ему пришлось осваивать сварку, делать самодельный станок или заказывать платы с распаянными светодиодами, чтобы сделать свои видеостены.

Но можно поступить проще, просто пришить ленту к шторам, как это делают китайцы:


Если плотность светодиодов взять побольше, то на таких шторах вполне можно смотреть кино (только посмотрите даташит на светодиоды, чтобы к нужному количеству километров ленты прикупить нужное количество контроллеров и блоков питания).


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

https://www.youtube.com/watch?v=WlkMbNnIECM

Такая лампа тоже вполне может служить новогодним подарком, зато ленты на нее уйдет гораздо меньше и повторяемость проще. Так что предлагаю изобрести еще и такую лампу. С процессом постройки физической части я не буду повторяться, ведь Bitluni все очень подробно описал. Я предлагаю повторить наш опыт с маппингом светодиодов.

Я возьму 3dsmax и нарисую простую сцену, примерно вот так:


При помощи описанной тут техники, я могу сгенерировать спрайты для каждой "лампочки", даже если они будут криво приклеены:


[spoiler]

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

[/spoiler]

Да, вы можете сделать эмулятор такой "вазы", рисовать пиксели в рантайме и... все? Не только! Используя полученные экранные координаты, вы можете спроецировать обратно любой рисунок или даже видео! Из ролика Bitluni я вырезал камин:


И наложил его на полученную маску. Накладывал самодельным фильтром, который размазывал цвета по шарикам:


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

Чем не подарок к новому году? Начинайте собирать прямо сейчас, чтобы успеть собрать себе подарок!

Заключение

Многие наверное хотят меня спросить: "а где же скриншоты с реального Флиппера, раз он у тебя уже на руках?". Увы, для этого сначала надо портировать код на реальное устройство. А для этого надо сначала изобрести механизмы отрисовки точек, линий и прочих примитивов. А для этого сначала надо изобрести механизм менеджмента видеопамяти, блиттинга слоев, механизмы управления экраном. А для этого сначала надо написать драйвер экрана. А для этого надо сначала разобраться с регистрами экрана (что в теории поможет делать то, что невозможно). А для этого надо перестать писать длинные статьи и начать работать. А для этого надо сбежать из дурдо^W^W^W^W^W^W^W

[spoiler]

Ну, еще и вот такая проблема. Снимал на микроскоп, лучше не получается. Я старался, честно:


Можно было бы попробовать снимать на макрокамеру в телефоне, но после прошивки GSI, осталось только 2 камеры из 4х, и макро в их число не входит. Чуть позже попробую найти видеооператора с крутой камерой и заплатить ему сотни денег.

[/spoiler]

Используемые инструменты

Меня часто спрашивают: "а в какой программе это было сделано?", потому вот основные инструменты из этой работы:

* mc (Midnight Commander) - написание кода

* gcc - компилятор кода

* ffmpeg - работа с видео, подача его в свои приложения

* virtualdub - обрезка видео, подгонка фильтров

* gifsicle / optipng - оптимизация изображений

* gimp - создание и кадрирование скриншотов

* 3dsmax - рисование трехмерной вазы (процедурно)

Исходники

К статье прилагается архив со всеми исходниками. Вы ведь знаете, что во время написания таких статей, образуется много кода? Так вот, если кто-то хочет поковыряться - добро пожаловать. Исходники надо причеса^W^Wпоставляются "как есть", без причесывания и последующего выкладывания, которое обычно происходит чуть позже, чем никогда. Исходники писались для того, чтобы достигнуть определенных целей, в том числе скорости разработки, потому особенно впечатлительных просьба не смотреть. Они без форматирования, без комментариев, многие вещи захардкожены, что-то не выполняется и вообще, нету инструкции как это запускать. Более того, исходники правились на живую, без какого-либо контроля версий, по мере написания статьи, так что некоторых вещей в них уже может не быть, или они закомменчены. И конечно же, сотни тысяч тонн копипасты. Тем не менее, эта статья написана, а значит свою задачу они выполнили. Если кому-то еще интересно - велкам, забирать здесь.

Другие работы

В процессе у меня и другие статьи, пока можете выбрать, что интереснее:

* Надо дописать статью про реализацию клавиатурки Флиппера

* Про композитинг и анимацию пользовательского интерфейса Флиппера

* Про систему локализации и отрисовку экранного меню Флиппера

* Про стриминг во Флиппер - как рисовать графику в Фотошопе и сразу видеть ее на реальном устройстве

* Про стриминг из Флиппера - как стримить видео с реального устройства на YouTube

Не гарантирую, что напишу о выбранной теме быстро, ведь меня заставляют еще и что-то ПОЛЕЗНОЕ делать, но я постараюсь!