ICC-профили в GIF

Формат ГИФ почему-то окружён каким-то нереальным количеством мифов, хотя до недавнего времени он был очень популярен, а для анимации до сих пор остаётся единственным универсальным форматом.

Например, считается, что анимация появилась только в версии 89a (неправда, она была уже в версии 87a, в 89а её возможности были расширены), что ГИФ может использовать не более 256 цветов (не совсем так — не более 256 цветов на кадр, кадры могут накладываться друг на друга, давая любое отображаемое компьютером количество цветов).

Другое распространённое заблуждение — что ГИФ не поддерживает цветовые профили (ICC).

В самом деле, если посмотреть спецификацию формата, то слово «ICC» там не найдётся, но не найдётся там и слова «loop» (здесь: количество повторов), тем не менее, вы видели анимации, где указано определённое количество повторений и браузеры как-то это понимают.

Дело в том, что формат ГИФ поддерживает расширения. Расширения в спецификацию формата не входят.

Упомянутое количество повторов анимации задаётся в расширении, которое было когда-то предложено фирмой «Нетскейп» и потому имеет заголовок «NETSCAPE2.0», у расширения, которое содержит цветовой профиль заголовок другой — «ICCRGBG1012». ГИФ всегда разбит на блоки, и блок расширения (любого) помечен специальным флагом; заголовок расширения позволяет просмотрщику (в нашем случае — браузеру) решить с каким из расширений формата он имеет дело.

Вот начало файла ГИФ с внедрённым профилем:

00000000 47 49 46 38 39 61 01 00 01 00 f0 00 00 fe 00 02 |GIF89a..........|
00000010 00 00 00 21 f9 04 00 00 00 00 00 21 ff 0b 49 43 |...!.......!..IC|
00000020 43 52 47 42 47 31 30 31 32 ff 00 00 03 f0 55 43 |CRGBG1012.....UC|
00000030 43 4d 02 40 00 00 6d 6e 74 72 52 47 42 20 58 59 |CM.@..mntrRGB XY|


Пока не все браузеры умеют поддерживать цветовые профили ГИФ (как и профили любого другого формата), так ФФ не научился делать это до сих пор, а «Опера» научится это делать в новой версии 12.50.

Проверить поддерживает ли браузер цветовые профили можно и через Джаваскрипт. Принцип такой: я загружаю ГИФ-изображение 1×1 с цветовым профилем в тег CANVAS, потом проверяю цвет точки, он должен быть определённого цвета. Проверить поддерживает ли ваш браузер цветовой профиль четвёртой версии ГИФ можно у меня на сайте, в разделе «Храню».
7 комментариев
29 августа 2012 07:19

Приватный режим в Сафари под iOS

Честно сказать, не знаю для чего это может быть нужно, но это одна из вещей, которая если понадобится, то потом не найдёшь. Джаваскрипт, который позволяет определить запущен ли мобильный Сафари (под Айос) в приватном режиме:
function isPrivateMode() {
    var s = window.sessionStorage, key = '';

    try {
        s.setItem(key, '');
        s.removeItem(key);
    } catch (e) {
        if (e.code === DOMException.QUOTA_EXCEEDED_ERR && !s.length) {
            return true;
        } else {
            throw e;
        }
    }
    return false;
}

// приватный режим включен?
alert(isPrivateMode());
Оригинальный код основан на следующем факте — в приватном режиме мобильный Сафари запрещает доступ к sessionStorage. Я только обернул скрипт в функцию и переименовал переменные.
2 комментария
1 августа 2012 08:41

Замыкания и Джаваскрипт

Все знают, что зона видимости в JS создаётся только внутри функций (если не брать в расчёт конструкцию «let» из JS 1.7):
var v = 5;

!(function() {
    var v = 6;
})();

alert(v); // будет 5
Я тут подумал, что ведь есть и второй способ (я как-то уже о нём упоминал, и хотя внутри происходят немного другие вещи, внешне выглядит похоже), который даже немного похож на вышеупомянутую конструкцию «let», вот сравните:
var x = 1, y = 0;

// конструкция «let», JS 1.7+
let (x = x+10, y = 5) {
    alert(x+y); // 16
}
alert(x+y); // 1

// конструкция «with»
with ({ x: x+10, y: 5 }) {
    alert(x+y); // 16
}
alert(x+y); // 1
Не так уж и много отличий, только выражением «with» быть не умеет, но можно было бы и научить.
7 комментариев
24 июля 2012 11:11

Как определить модель iPad через JavaScript?

На «Хабре» наткнулся на любопытный код на JS, который определяет с каким Айпадом мы имеем дело — первым, вторым или третьим. Третий ото всех остальных отличить просто — у него в свойстве «devicePixelRatio» значение „2“ записано. А первый от второго как?

Оказывается способ есть — надо в событии «devicemotion» проверить есть ли у устройства акселерометр. Не знаю для чего это всё может пригодиться (разве что отключать какие-то ресурсоёмкие спецэффекты), но знать о том, что такая возможность проверки есть, не помешает.

Я немного переписал код, сделал из него функцию, чуть упростил и навёл порядок. Получился вот такой код:
// функция для определения модели Айпада
function detectIPadVersion(callback, undefined) {
    if ('ondevicemotion' in window) {
        window.addEventListener('devicemotion', function self(event) {
            if (~navigator.platform.indexOf('iPad')) {
                var version = event.acceleration ? (window.devicePixelRatio === 2) + 2 : 1;
            }
            window.removeEventListener('devicemotion', self);

            callback(version);
        }, false);
    } else {
        callback(undefined);
    }
};

// пример вызова, в callback будет передан номер модели (1,2 или 3) или undefined, в случае, если это не iPad
detectIPadVersion(alert);

Как в JS узнать что объект — массив?

Узнал на семинаре Кантора такую штуку. Как узнать, что объект, который пришёл на вход — массив? Конечно, лучше использовать утиную типизацию, но если нам нужен именно массив, со всей полнотой методов, не проверять же каждый?

Кстати, если кто-то думает, что ответ «typeof», то он просто не знает JS. Эта конструкция вернёт «object».

Я до недавнего времени думал, что правильный способ такой:
console.log([] instanceof Array); // true
А редко его используют, потому что конструкция «instanceof» не везде реализована. Я заблуждался. Вот правильный способ:
console.log(Object.prototype.toString.call([]) == '[object Array]'); // true
И вот почему. Оказывается, у каждого фрейма своя иерархия типов, если один фрейм передаёт во второй какой-то объект, то «instanceof» даст «false», так как сравнивает c «Array», локальным для этого фрейма. Проблему иллюстрирует следующий пример:
<script>
function Up(arr, iframeWindow) {
    alert(arr instanceof Array); // false
    alert(arr instanceof iframeWindow.Array); // true

    alert(Object.prototype.toString.call([]) == '[object Array]'); // true
}
</script>
<iframe src="data:text/html, <script>parent.Up([], window)</script>"></iframe>
15 комментариев
28 апреля 2012 18:14

Логичность шаблона «объявили и вызвали» в JS

Вчера в комменариях возникло обсуждение семантики синтаксиса «!function () {}()». Мне так кажется, что семантика здесь побоку, использовать функцию для создания зоны видимости — уже костыль (в JS 1.7 есть конструкция «let» для этого), но мне тут подумалось, что ведь можно сделать эту конструкцию семантичной, если использовать забытую конструкцию «with»:
with (function () {
    // тело функции
}) call();
На мой взгляд, весьма и весьма семантично. Что скажете?
5 комментариев
26 апреля 2012 08:19

Декларация-вызов в JS

Сегодня на первой части семинара Ильи Кантора по JS присутствовал (к сожалению, на второй части не получится, буду в Москве это время). Немного поговорили о частом паттерне «объявили функцию и тут же вызвали». Используется этот приём (если кто не знаком с ДжаваСкриптом) для изоляции — в этом языке функции единственное, что может создавать зоны видимости.

Я вскользь затронул свой любимый способ — описание функции и вызов через восклицательный знак или тильду, вместо оборачивания скобками. Вообще, в JS непосредственный вызов функции сразу по месту определения возможен, только если декларация находится в контексте выражения. Для этого и нужны традиционные скобки:
(function () {
    // скобки вокруг функции говорят интерпретатору, что это выражение
})();
Вообще говоря, можно использовать любые средства, чтобы декларация стала частью выражения. Например, можно что-то прибавить к декларации. Или использовать любую унарную операцию.

Использование тильды или восклицательного знака гораздо лучше скобок по трём причинам:
!function () {
    // 
}();
Во-первых, на один байт меньше. Если таких функций много, экономия существенная. Во-вторых, скобки надо балансировать, а унарный оператор — нет.

И, в-третьих, самая главная причина. Вот такой код вызовет ошибку:
var a = 5
(function() {})()
Причём ошибку, которую найти иногда довольно трудно, да и сообщение о ней не самое дружественное: «TypeError: 5 is not a function». На самом деле, тут пропущена точка с запятой. Интерпретатор считает, что ему встретились две части выражения, между которыми пропущен какой-то знак. Эта ситуация часто встречается при автоматическом объединении двух скриптов в один.

Обычно для этого добавляют точку с запятой сразу перед такими блоками.

Но если использовать унарный оператор, то этой проблемы просто нет (поэтому, на самом деле, мы экономим два байта, ещё и точка с запятой не обязательна):
var a = 5
!function () {}()
Тут интерпретатор, встретив унарный оператор, понимает, что предыдущее выражение закончилось и началась новая конструкция.
33 комментария
23 апреля 2012 21:16

UserJS для воспроизведения H.264 в «Опере»

Существует плагин «DivX web player», который отлично подменяет тег «VIDEO», в случае, если должен быть использован кодек H.264, но браузер его не поддерживает.

К сожалению, в «Опере» он почему-то не работает. Я сделал UserJS, который, пользуясь этим плагином, подменяет видеотег, если у него указан тип «video/mp4». Все атрибуты он не переносит (я просто пока не столкнулся с тем, что это нужно), буду добавлять, если столкнусь. Кроме того, я подменяю метод «canPlayType», чтобы показать, что браузер H.264 поддерживает.

Самая засада — это тег «VIDEO» без размеров. Мне для поддержки этого приходтся запускать проигрывание и когда становятся доступны медиаданные, останавливать воспроизведение и переписывать размеры видео.
// ==UserScript==
// @name        XVid patch
// @author      Evgeny Stepanischev aka Bolk
// @version     1.00
// @namespace   http://bolknote.ru/files/opera-xvid-patch.js
// @modified    2012-04-14
// @include     http://*
// ==/UserScript==

!function () {
    var htmlif = function (attr, attrname) {
        return attr ? attrname + '="' + attr + '" ' : '';
    }

    var patchtag = function (o, src) {
        var html = '<embed type="video/divx" '+
        'custommode="none" ';

        if (o.preload == 'auto') html += 'bufferingMode="full" '; else
        if (o.preload == 'none') html += 'bufferingMode="null" ';

        html += htmlif(o.poster, 'previewImage')+
                htmlif(o.id, 'id')+
                htmlif(o.name, 'name')+
                htmlif(src || o.src, 'src')+
                htmlif(o.controls ? 'full' : 'null', 'mode')+
                htmlif(o.width, 'width')+
                htmlif(o.height, 'height');

        if (!o.height || !o.width) {
            html += 'onloadedmetadata="this.width=this.videoWidth;this.height=this.videoHeight" ';
            html += htmlif(!o.autoplay && 'if (!this._sized) {this.pause(); this.currentTime=0; this._sized=1}', 'onplay');

            html += 'autoPlay=true ';
        } else {
            html += htmlif(o.autoplay ? 'true' : 'false', 'autoPlay');
        }

        html += 'pluginspage="http://go.divx.com/plugin/download/"></embed>';

        o.outerHTML = html;
    };

    var patch = function () {
        window.opera.addEventListener('BeforeScript', function() {

            with (window.HTMLVideoElement)
            if (prototype._canPlayType === undefined) {
                prototype._canPlayType = prototype.canPlayType;

                prototype.canPlayType = function (type) {
                    if (type.substr(0, 9) == 'video/mp4') {
                        return 'probably';
                    }

                    return this._canPlayType(type);
                }
            }

            !function() {
                var videos = document.getElementsByTagName('VIDEO');

                for (var i = 0, vl = videos.length; i<vl; i++) {
                    if (!videos[i]) continue;

                    if (videos[i].type && videos[i].type.substr(0, 9) == 'video/mp4') {

                    } else {
                        var sources = videos[i].getElementsByTagName('source');

                        var foundSupported = false, foundMP4 = false;

                        for (var j = 0, sl = sources.length; j < sl; j++) {
                            if (videos[i]._canPlayType(sources[j].type) !== '') {
                                foundSupported = true;
                                break;
                            }

                            if (sources[j].type.substr(0, 9) == 'video/mp4') {
                                foundMP4 = sources[j].src;
                            }
                        }

                        if (!foundSupported && foundMP4) {
                            patchtag(videos[i], foundMP4);
                        }
                    }
                }
            }();
        });
    };

    for (var i = 0, l = navigator.plugins.length; i < l; i++) {
        if (navigator.plugins[i].filename == 'DivXBrowserPlugin.plugin') {
            return patch();
        }
    }
}();
Добавлено: в комментариях справдливо указали на недостаток — перехватывать надо не только BeforeScript. Исправлять не буду до первого реального случая, когда где-то этого события не будет достаточно.
13 комментариев
15 апреля 2012 13:18

Работа с камерой и распознавание чисел в «Опере»

Распозновалка (43.68КиБ) Я вчера заморочился на отличненько. Мне показалось скучным и грустным вводить цифры счёта за квартиру в окошко «Госуслуг» и решил сделать автоматическую распознавалку чисел, попутно изучив работу с камерой из браузера.

Доступ к камере — эксперментальный стандарт, поэтому мало кто из браузеров может им похвастаться, я использовал специальный билд «Opera-Labs-Camera», который можно взять с сайта снапшотов «Оперы». Работать мой пример, соответственно, будет только там.

АПИ оказалось несложным. Всё, что нужно сделать — соединить специальным образом тег VIDEO с источником стримового видео; потом я 10 раз в секунд забираю кадр с видео и кладу в CANVAS (на скриншоте он цветной), оттуда вырезаю небольшую область, перевожу в градации серого, беру за порог 75% от усреднённого цвета и перевожу всё в ч/б.

Больше всего из трёх часов, которые у меня ушли на это, я делал распознование цифр. Алгоритмы, которые я нашёл в интернете, были очень монструозными, да и переписывать готовое было скучно, поэтом я перепробовал несколько вариантов, которые пришли мне в голову.

В итоге, лучше всех распознаёт следующий алгоритм.

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

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

На скриншоте в большом текстовом поле записаны получившиеся комбинации. Первое — номер по порядку, далее (через двоеточие) — количество переходов по вертикали и потом описанная последовательность. Например, «ноль» записывается как «2:11» (два перехода по вертикали, потом одинаковые переходы по разу слева и справа разделительной линии).

Алгоритм «шумит», из-за того, что дрожит рука с бумагой, из-за освещения. Поэтому я накапливаю статистику и указываю ту цифру, которая в этом позиции распозначалась больше других. Статистику сбрасываю, если количество объектов на экране изменилось.
9 комментариев
6 апреля 2012 12:50

Каррирование и частичное применение

Я, оказывается, путал раньше каррирование и частичное применение. Даже не то чтобы путал, просто не пытался разобраться в чём разница между терминами, применяя то и другое по мере надобности. Недавно на «Хабре» в комментариях мне на моё незнание указали и я уяснил, наконец, что конкретно стоит за каждым термином.

Каррирование — преобразование функции от двух аргументов в функцию от первого аргумента, возвращающую функцию, результат вызова которой со вторым агрументом эквивалентен вызову первоначальной функции с упомянутыми аргументами. Немного запутанно, с примерами станет всё яснее. Я буду объяснять на примере Джаваскрипта, его больше народу знает, а на ПХП очень уж многословно получается. Кстати, в соответствующую статью в «Википедии» я добавил пример на «Гугл Гоу».
// функция, суммирующая два числа
function sum(x, y) {
    return x+y;
}

alert(sum(2, 8)); // вернёт 10

function sum(x) {
    return function (y) {
        return x+y;
    }
}

alert(sum(2)(8)); // тоже вернёт 10
При вызове второй функции sum создаётся функция, в которую из-за замыкания, «железно» попадает аргумент «x» со значением «2». Поэтому возвращаемая функция будет всегда суммировать с двойкой. С этим фактом тесно связано частичное применение, потому что оно только что, фактически, состоялось.

Частичное применение функции (Partial function) — создание некой функции на базе указанной, где некоторые агрументы заменены конкретными значениями. Например:
function partial_one_arg(f, a) {
    return function(b) {
        return f(a, b);
    }
}

min0 = partial_one_arg(Math.min, 0);
min1 = partial_one_arg(Math.min, 1);

alert([min0(-5), min0(5)]); // вернёт [-5, 0]
alert([min1(-5), min1(5)]); // вернёт [-5, 1]
В примере я заменил один из агрументов фунции нахождения минимального значения на конкретное значение, теперь при вызове функции «min0» сравнение будет идти с нулём, при вызове «min1» — с единицей.

Очень простые штуки, часто встречающиеся в функциональном программировании.
8 комментариев
6 марта 2012 19:38

%u → UTF-8

Столкнулся сегодня с задачей — надо сконвертировать пришедшую из JavaScript строку (в ней символы Юникода в виде «%uXXXX») в UTF-8 на PHP. Придумал, на мой взгляд, самый короткий способ это сделать:
echo json_decode(str_replace('%u', '\\u', json_encode($str_from_javascript)));
10 комментариев
3 февраля 2012 10:57

С Новым годом, что ли?

Я тут, чтобы мозг совсем не расслаблялся на праздниках, убил около часа и вручную сделал на Яваскрипте небольшое поздравление с Новым годом:
[][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})
[-~[]]+([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+
!![]]+([]+!![])[+![]]+([]+!![])[+!![]]+
([]+!![])[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+
!![])]+([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])
[+!![]]][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})
[-~[]]+([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+
([]+!![])[+![]]+([]+!![])[+!![]]+([]+!![])
[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+
([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])[+!![]]]
(([]+!![])[+!![]]+([]+{})[-~[]<<[-~[]+-~[]]]+
([]+!![])[-[]]+([]+[][{}])[-[]]+([]+!![])[+!![]]+
([]+[][{}])[-~[]]+([]+{})[~[]+(+!![]<<!![]+!![]+
!![])]+([]+![])[!![]+!![]+!![]]+([]+![])[!![]<<!![]+
!![]]+([]+![])[!![]+!![]]+([]+![])[-[]])()
[([{}-{}]+[])[-~[]]+([]+![])[!![]+!![]]+([]+![])
[!![]<<!![]+!![]]+([]+!![])[-~[]]+([]+!![])[+[]]](
[][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})
[-~[]]+([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+
!![]]+([]+!![])[+![]]+([]+!![])[+!![]]+
([]+!![])[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+
([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])[+!![]]]
[([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+
([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+
([]+!![])[+![]]+([]+!![])[+!![]]+([]+!![])
[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+
([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])[+!![]]]
(([]+!![])[+!![]]+([]+{})[-~[]<<[-~[]+-~[]]]+
([]+!![])[-[]]+([]+[][{}])[-[]]+([]+!![])
[+!![]]+([]+[][{}])[-~[]]+([]+{})[~[]+(+!![]<<!![]+
!![]+!![])]+([]+![])[!![]+!![]+!![]]+([]+![])
[!![]<<!![]+!![]]+([]+![])[!![]+!![]]+([]+![])
[-[]])()[([]+![])[-~[]]+([]+!![])[+![]]+([]+{})
[-~[]]+([]+{})[!![]+!![]]](([]+![])[-~[]]+
([][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+
([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])
[+![]]+([]+!![])[+!![]]+([]+!![])[-[~[]+~[]]]+
([]+{})[-~[]+(!![]<<!![]+!![])]+([]+!![])[+![]]+
([]+{})[-~[]]+ ([]+!![])[+!![]]]+[])[(-~[]+[+![]])-!![]])+
([]+![])[-~[]]+
(/!/[([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+([]+[][{}])
[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])[+![]]+([]+!![])[+!![]]+([]+!![])
[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+([]+!![])[+![]]+([]+{})
[-~[]]+([]+!![])[+!![]]]+[])[+!![]+[!![]<<!![]-~[]]]+
(/!/[([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+([]+[][{}])
[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])[+![]]+([]+!![])[+!![]]+([]+!![])
[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+([]+!![])[+![]]+([]+{})
[-~[]]+([]+!![])[+!![]]]+[])[+!![]+[!![]<<!![]-~[]]]+
([]+[~[]/[]])[[[!![]+!![]]*[!![]+!![]]]*[!![]+!![]]]+
([]+{})[~[]+(+!![]<<!![]+!![]+!![])]+
([]+[][{}])[-~[]]+
([]+{})[-~[]<<[-~[]+-~[]]]+
([][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})
[-~[]]+([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])
[+![]]+([]+!![])[+!![]]+([]+!![])[-[~[]+~[]]]+([]+{})[-~[]+
(!![]<<!![]+!![])]+([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])
[+!![]]][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+
([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])[+![]]+
([]+!![])[+!![]]+([]+!![])[-[~[]+~[]]]+([]+{})[-~[]+
(!![]<<!![]+!![])]+([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])
[+!![]]](([]+!![])[+!![]]+([]+{})[-~[]<<[-~[]+-~[]]]+
([]+!![])[-[]]+([]+[][{}])[-[]]+([]+!![])[+!![]]+([]+[][{}])
[-~[]]+([]+{})[~[]+(+!![]<<!![]+!![]+!![])]+([]+![])
[!![]+!![]+!![]]+([]+![])[!![]<<!![]+!![]]+([]+![])
[!![]+!![]]+([]+![])[-[]])()+[])[([]+![])[!![]+!![]+
!![]]+([]+![])[!![]+!![]]+([]+[][{}])[!![]+(!![]<<!![]+!![])]+
([]+{})[!![]+(!![]<<!![]+!![])]+([]+!![])[!![]+!![]+!![]]](~[]+~[])[-[]]+
([]+{})[~[]+(+!![]<<!![]+!![]+!![])]+
(~[]/[]+[])[-~[]<<-(~[]+~[]+~[])]+
([~[]<[]]+[])[-[~[]+~[]+~[]]]+
([{}-{}]+[])[-~[]]+
 ([]+!![])[+!![]]+
(/!/+[])[-~[]])
Запускать лучше всего в консоли браузера (в Firebug, Dragonfly и так далее). Цели сократить код у меня не было, более того, кое-где для разнообразия (буквы-то повторяются) я придумывал разные конструкции, чтобы не так скучной было. С Новым годом!

Если кого-то поразило это буйство скобочек, то намекаю ещё раз — это полноценная программа на Яваскрипте.
24 комментария
5 января 2012 10:51

Почему не следует расширять встроенные типы JavaScript

На Хабре хорошая статья — «Monkey-Patching, или Расширение Встроенных Типов: религия или осознанный выбор?».
Комментировать
16 ноября 2011 08:41

Поддержка MJPEG в «Опере»

Как я уже писал, «Опера» иначе показывает MJPEG, не как остальные браузеры. Если открыть какую-нибудь веб-камеру, использующую этот формат, в десятой версии, то «Опера» сначала задумается, потом покажет какой-то кадр, 11 и 12 версии показывают ещё хуже — подобие потоковой передачи уже есть, но кадры лепятся один на другой и ничего разглядеть невозможно.

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

Вдохновившись библиотекой добавляющей нативную поддержку APNG в браузер, я подумал, что я ничем не хуже и смогу добавить поддержку MJPEG в «Оперу». Получился небольшой userjs, который я, естественно, выложил на «Гитхаб».

Идея такая — находим картинку во всех тегах IMG, у которой расширение «mjpg». Через XHR получаем с сервера видеопоток, копим кадр, подменяем найденную картинку и так далее.

Работает начиная с 12 версии браузера, которая ещё не вышла (кстати, в позавчерашней сборке сломали userjs напрочь) — в предыдущих версиях XHR слабоват по возможностям для этого.

Больше всего я бился с декодированием бинарных данных. Последняя версия «Оперы» поддерживает тип ответа arraybuffer (а вот blob не поддерживается), но если его выбрать, то данные в цикле почему-то перестают накапливаться, вероятно какой-то баг браузера. Я долго мучался, прежде чем придумал следующий хак — если сделать overrideMimeType('image/jpeg'), то данные приходят в двухбайтовой кодировке UCS-2, которую несложно декодировать.

Кстати, быстрее всего у меня получилось декодировать не циклом, а регулярными выражениями, дальше тормозит уже не мой код, а способ получения отдельного кадра — каждый раз я соединяюсь с сервером заново. Можно бы накапливать кадры пачкой и потом их сбрасывать, но у меня нет задачи смотреть трансляции футбола, у меня дом, который не бегает и полученной скорости мне вполне хватает (где-то 2-3 кадра в секунду).

В общем, получился довольно небольшой код — 94 строки. Посмотреть код.
7 комментариев
9 октября 2011 04:16

IEWebGL

Микрософт пока не собирается реализовывать WebGL в своём браузере, так как считает, что технология не устоялась (и даже называла её небезопасной). Для тех, кто почему-либо хочет пользоваться IE, но хочет попробовать WebGL, есть специальный плагин IEWebGL, недавно вышел релиз.

Плагин показывает отличную производительность (до 100 fps под IE9 и 10PP, до 60 — под IE8), работает начиная с IE6, не требует OpenGL и менять в HTML или JavaScript сайта так же ничего не требуется.
7 комментариев
31 августа 2011 16:23