понедельник, 29 декабря 2008 г.

SXEmacs - behind the scene

Как и обещал приоткрою немного занавес SXEmacs и расскажу, а точнее покажу, как устроена модель разработки SXEmacs. Но сначала про разработчиков:

hroptatyr (aka Sebastian Freundt)
Немец, живёт в Берлине, математик, занимается разработкой систем компьютерной алгебры, зарабатывает на жизнь игрой на бирже, любит мощные компьютеры и большие мониторы, носит очки
lg (aka Zajcev Evgeny)
Гимнаст, работал охранником 6 лет, хорошо владеет рукопашным боем, неплохо обращается с холодным оружием и отлично стреляет. Живёт в лесистой части г. Ульяновск, любит работать в туалете, выпивать, читать книжки и детей
njsf (aka Nelson Ferreira)
Живёт в Америке, любит фотографировать и путешествовать, есть красивая подружка, имеет хорошее чувство юмора.
PeanutHorst
Не совсем разработчик, а скорее противовес в команде SXEmacs, пессимистичен, работает в OpenBox под FreeBSD, часто несёт глупости, тестирует SXEmacs на различных платформах.
JackaLX (aka Steve Youngs)
Создал SXEmacs, домохозяин, имеет кучку детей, держит магазин, торгующий шмотками и вещами с символикой SXEmacs, ждёт когда кто-нибудь переведёт первый доллар в качестве денежного пожертвования на развитие SXEmacs, есть красавица жена Мишель с большой грудью, которая зарабатывает на жизнь семье

Это список активных разработчиков на текущий момент, так что он не совсем полный, но для понимания поднаготщины — хватит.

Итак, скачайте эту картинку и откройте её в просмоторщике в полном формате, т.е. без использования fill-to-width или схожих функций.

Те кому интересны все детали можете скачать SVG исходник

ReST source Скачать оригинал

вторник, 23 декабря 2008 г.

Условия гонки в ASYNEQ

Давайте для начала я покажу вам скриншот моего SXEmacs:

Скриншот

Ничего необычного не замечаете? У меня почти невидимые клеточки. Цвет #C4C4C4 на фоне #CCCCCC да ещё с моим дальтонизмом — я их действительно еле вижу, главное что я знаю, что они там есть и если приглядеться, то я их отчётливо вижу. Особо внимательные заметят, что в каждую клеточку помещается 4 символа по горизонтали и 2 по вертикали.

Теперь немного утомительной истории. Путём наблюдений и экспериментов я пришёл к выводу, что у меня есть некая особенность — проблемы лучше решаются, если во время размышлений вести записи на листочке в клеточку. Первый раз я это заметил решая просто гигантское количество задач по планиметрии в 8 классе. В дальнейшем доходило даже до маразма; для того, чтобы решить одну задачу на вступительных экзаменах в МГУ мне пришлось разлиновать в клеточку выданный листочек. Я всё не мог понять с чем это связано, и вот, когда начал использовать XEmacs в году '97-98, то понял. Как сейчас помню, сидел набирал текст какой-то программы и возникло острое ощущение, что буквы, которые я так старательно набираю, сейчас разбегутся, а вместе с этими буквами разбегутся и вкладываемые мысли. Тогда то я и понял, что хорошие мысли порождаются свободным разумом, но истинно свободный разум также легко теряет мысли как и порождает их, для разума-рабочей лошадки, нужна «клетка». Вот такая вот психология.

Со временем, поэкспериментировав с цветом и размером клеток в окнах (S)XEmacs я нашёл оптимальный (дающий наибольшую продуктивность) для себя вариант — тот, что на картинке.

И вот представьте, сижу я как обычно печатаю текст, пытаясь вложить в него хоть немного смысла, как вдруг, буквы, в прямом смысле этого слова, начинают убегать и смешиваться друг с другом. Всё думаю, доигрался, колёсики уехали. Как ни странно, но первая мысль была именно, что проблемы у меня, а не у SXEmacs. Немного успокоившись задался вопросом почему так произошло, ведь раньше такого не было. Нашёл особенность — использовался flyspell режим, который обычно выключен. Покопавшись в flyspell нашёл особенность, что self-insert команда может породить вызов accept-process-output. Дальнейшее разбирательство привело к ASYNEQ. А текст так и не написал :(

ReST source Скачать оригинал

UPD: Код для сетки

(defface lg-grido-face
  '((((class color) (background dark))
     (:foreground "gray10"))
    (((class color) (background light))
     (:foreground "gray77"))
    (t (:foreground "gray77")))
  "Face for grid.")

(push '("grido" (face-foreground 'lg-grido-face)) xpm-color-symbols)

(defconst lg-square-64x64-xpm
  (concat
   "/* XPM */\n"
   "static char *mini_square_xpm[] = {\n"
   "/* columns rows colors chars-per-pixel */\n"
   "\"64 64 2 1\",\n"
   "\"       c None s background\",\n"
   "\".      c gray77 s grido\",\n"
   "/* pixels */"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"................................................................\"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "};"))

(set-face-background-pixmap
 'default
 (make-image-specifier (vector 'xpm :data lg-square-64x64-xpm))) 

понедельник, 22 декабря 2008 г.

SXEmacs - лучший программный продукт года

Решил немного помечтать и вообразить, что нужно SXEmacs, чтобы выиграть приз «Лучший программный продукт года». Было ли вообще такое, чтобы GNU Emacs выигрывал этот приз? Что же необходимо изменить и сделать в SXEmacs, чтобы это событие произошло?

  • Кардинально улучшить подсистему обработки событий (event loop).
  • Реализовать подсистему отображения (redisplay) используя современные средства, например Cairo. Использовать современные библиотеки виджетов, например GTK+.
  • Реализовать нормальную мультиязыковую поддержку.
  • Поймать все утечки памяти при использовании BDWGC и сделать его сборщиком мусора по умолчанию.
  • Реализовать чистую интеграцию различных ЯП, таких как Python, Ruby и др.
  • Улучшить документацию как для разработчиков так и для пользователей.
  • Создать более продвинутый и быстрый сопоставитель текста с образцом на замену регулярных выражений. На базе него сделать, чтобы font-lock работал корректно и быстро.
  • Создать систему совместной разработки на базе SXEmacs.

info

Призываю читателей к дискуссии. Опишите свой взгляд, расскажите каким вы хотели бы видеть ([S]X)Emacs.

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

Подсистема событий

Самое удивительное, что сейчас вообще нет этой подсистемы ;). Есть определённые части, выполняющие необходимые действия, но отсутствует целостность, расширяемость и сила. В существующей ПС есть только высокий уровень доступа, нет разделения API на низкий/высокий уровни, поэтому если требуется что-то добавить, то необходимо делать много низкоуровневой мишуры, с помощью копи&паст программирования — утомительно.

В SXEmacs было решено использовать libev как низкоуровневую базу для ПС. Выбор стоял между liboop, libevent, libev и одной проприетарной библиотекой, которую планировалось перелицензировать (уже было получено согласие правообладателя). liboop отпала первой — библиотека практически мертва. libevent является почти de-facto стандартом для создания софта с ПС, но у нескольких разработчиков SXEmacs был негативный (как и позитивный тоже) опыт использования libevent и как раз ограничения, которые существуют в ней плюс просто ошеломляющие результаты тестирования скорости libev в сравнении с libevent не позволили выбрать libevent. Проприетарная библиотека отпала так как она зависела от дополнительной большой библиотеки, а так же в ней не очень удобный API (хотя, видимо, очень мощный) и отсутствовала документация.

Так зачем вообще всё это нужно? А для того, чтобы полностью избавиться от внутренних блокировок в SXEmacs. Блокировки бывают двух типов:

  1. Выполнение elisp кода приостанавливается в ожидании какого-то события.
  2. Блокирует сам вызов библиотечной функции.

К пункту 2 относятся вызовы для разрешения DNS имён, такие как gethostbyname(3) и getaddrinfo(3), вызов connect(2), а также, как это не удивительно, вызов write(2). В GNU Emacs реализован неблокирующий вызов connect если передано ключевое слово :nowait1 в процедуру make-network-process, при этом вызов make-network-process всё же может заблокировать, если заблокировало разыменование имени хоста. В SXEmacs вызов connect всегда блокирует, поэтому open-network-stream всегда блокирует. Что касается write(2), то по настоящему всё же write не блокирует, ибо дескриптор был переведён в неблокирующий режим для неблокирующего чтения, но все Емаксы, получая EWOULDBLOCK от write(2), делают select в ожидании, когда разрешат записать.

К пункту 1 относятся любые вызовы accept-process-output и call-process. Что произойдёт если accept-process-output ожидает данных и в этот момент приходит какое-нибудь событие? Если событие системное, то оно обрабатывается немедленно, если событие специальное или командное2, то оно будет отложено и записано в очередь событий, которые будут обработаны позже.

Асинхронный обработчик событий

В SXEmacs есть такая вещь называется ASYNEQ — это возможность асинхронной обработки событий. Не всякое событие может быть обработано асинхронно. В общем случае асинхронная обработка командных событий ведёт к условиям гонки (race condition)3. Специальные же события без проблем можно обрабатывать асинхронно. Именно по этой причине xlib и xwem работают более предсказуемо под SXEmacs, ибо они порождают просто тонны специальных событий, которые в XEmacs обрабатываются синхронно как и командные.

Часто командные события всё же можно обрабатывать асинхронно. Условия гонки были обнаружены токмо спустя год после того как в SXEmacs появился ASYNEQ. Это говорит о том, что условия гонки крайне редки и сильно зависят от специфики elisp кода, то есть автоматически невозможно определить возникнет ли в конкретном случае race condition или нет. Возможно, необходимо определить переменную asyneq-ignore-command-events, которую можно связать со значением t в особых случаях, когда необходимо запретить асинхронную обработку командных событий.

Как организовать новую ПС

Что мы хотим от новой ПС:

  • Целостность — одно место обработки событий, отсутствие костылей на вроде как при потенциальном блокировании write в process-send-string
  • Простой и мощный API как с низкоуровневыми, так и с высокоуровневыми конструкциями
  • Возможность интеграции других подсистем событий в ПС SXEmacs. Для начала X11 и Glib
Схема ПС

Сх. 1. пример работы ПС SXEmacs (event-loop.svg)

Использовать новую ПС будут:

  • На низком уровне
    • Асинхронное разыменование DNS
    • Остальные потенциально блокирующие вызовы
    • Клей для других ПС
  • На высоком уровне
    • Event API на elisp уровне
    • Таймеры
    • Прогнозируемые предикаты
    • И т.д.

Попробую изобразить в виде схемы, что будет происходить при возникновении командного события, которое порождает вызов open-network-stream. Как видим, событие помещается в очередь событий и когда до него дойдёт очередь, оно будет обработано, что породит вызов процедуры open-network-stream, которой, в свою очередь, необходимо разыменовать имя хоста и подсоединиться к нему, т.е. произвести две потенциально блокирующих операции. За время выполнения open-network-stream мы два раза посетим обработчик событий и в случае, если нам действительно нужно будет ждать, мы сможем обработать ещё кучу событий из очереди.

Именно так я и вижу работу новой ПС в SXEmacs.


1Вроде RMS был ярым противником ключевых слов
2Командные события порождаются действиями пользователя (нажал кнопку, мышкой побаловался и т.д.)
3Не буду описывать подробности, но, поверьте мне, я думал что помешался когда наблюдал эти условия гонки при работе с flyspell. Впрочем, психологическую составляющую этого опыта можно вынести отдельным постом, будет интересно

ReST source Скачать оригинал

Расширение REPL

Возникла тут идея реализовать возможность расширения возможностей REPL в SXEmacs, а особенно его Read части. Но сначала про Print часть.

Пользовательская печать

В XEmacs уже давно были предпосылки к созданию пользовательской печати elisp-объектов. Так, например, при задании структуры с помощью defstruct можно было указать :print-function — процедуру, которая осуществляет печать объекта данной структуры. Уже давно существовала магическая переменная custom-print-functions со следующим напутствием:

This variable is not used at present, but it is defined in hopes that a future Emacs interpreter will be able to use it.1

—custom-print-functions (cl.el)

В SXEmacs это напутствие было выполнено и появилась возможность пользовательской печати. Это моментально решило проблему печати сложно-рекурсивных структур, которая до этого решалась с помощью изменения переменных print-level и print-length.

Уже существуют пакеты, которые используют возможность пользовательской печати. Использовать эту возможность крайне легко:

(defstruct (test-prs
            (:print-function
             (lambda (tp s pl)
               (princ (format "#<test-prs %s>"
                              (test-prs-name tp)) s))))
  type              ; type of struct: `frs', `snd', ..
  (name "default")  ; name of object
  state             ; state of object
  plist             ; user defined properties list
  )
(make-test-prs :name "Done")
==> #<test-prs Done> 

Пользовательское чтение

Раз у нас есть пользовательская печать, то почему бы нам не иметь пользовательского чтения. В SXEmacs была реализована возможность пользовательского чтения под кодовым названием ureaders (от user readers). В новой встроенной (built-in) переменной ureaders хранится список пользовательских читалок. Пользовательская читалка это пара, состоящая из имени и функции-читалки от одного аргумента, которая возвращает elisp-объект или порождает ошибку. Приведу пример. Допустим, мы хотим чтобы следующая конструкция работала:

(setq mm '#<test ao -- 1
                 test -- 2
                 val -- "test">)
==> ((val . "test") (test . 2) (ao . 1)) 

реализуем:

(defun my-test-reader (input)
  (flet ((string-trim (s)
           (replace-in-string s "\\(^[ \t]+\\|[ \t]+$\\)" "")))
    (let ((ss (mapcar 'string-trim (split-string input "\n")))
          (rv nil))
      (dolist (s ss)
        (let ((rw (mapcar 'string-trim (split-string s "--"))))
          (push (cons (intern (car rw)) (read (cadr rw)))
                rv)))
      rv)))

(push '("test" . my-test-reader) ureaders) 

На первый взгляд эта возможность кажется не совсем нужной, ведь у нас и так есть мощнейшее средство — макросы. Это так, конечно, но в связке с пользовательской печатью почти прямой доступ к кишкам чтения выглядит интересно. Позже я продемонстрирую захватывающие вещи, которые можно реализовать с помощью ureaders.


1Эта переменная пока не используется и была создана в надежде, что в будущем интерпретатор Emacs сможет её использовать.

ReST source Скачать оригинал

пятница, 19 декабря 2008 г.

Просмотр страничных файлов в Wand-mode

Недавно в Wand-mode была добавлена возможность просмотра страничных файлов, таких как PDF, EPS, MPEG и т.д., у которых есть несколько страниц. Навигация по страницам до ужаса проста:

  • PgDown - Следующая страница
  • PgUp - Предыдущая страница
  • Home - Первая страница
  • End - Последняя страница
  • g или M-g - Переход на выбранную страницу. Передать номер страницы можно посредством универсального аргумента или ввести интерактивно.

Конечно, Wand-mode это вам не xpdf или gv, но для быстренького ознакомления с документом или быстренького выковыривания нужной картинки из книжки или видео вполне сгодится.

Текущая страница и общее количество страниц будет отображено на экране если у вас non-nil Wand-mode-show-fileinfo.

Заметим, что операции над картинкой будут осуществляться только для текущей страницы, а не для всех страниц файла.

ReST source Скачать оригинал

Wand-mode - алгоритм устранения красных глаз

Внимание!

Для корректного отображения этой страницы вашему браузеру необходима поддержка формата MathML.

Как и обещал напишу про то, как улучшить алгоритм устранения красных глаз у Wand-mode и как его тестировать. Возможно, что найдутся желающие его поковырять и улучшить ибо я не эстет да ещё и дальтоник к тому же, так что доволен тем, что есть и сейчас.

Описание алгоритма

Вкратце, проходим по всем пикселям выделенной области и смотрим на их красноту. Если пиксел достаточно красный, то заменяем его на тёмный. Затем немного размываем пикселы лежащие внутри эллипса, вписанного в выделенную область. Определение красности пиксела и его замена на более тёмный происходит в функции Wand-fix-red-pixels. Аргумент PIXELS, который она принимает — это список троек, где каждая тройка имеет вид (RED GREEN BLUE). RED, GREEN и BLUE принимают значения от 0 до 255.

После того как пикселы подправлены краснота уйдёт, но кое-где могут остаться резкие перепады цвета. Для того чтобы их сгладить мы накладываем эллиптическую маску (ибо зрак, в основном, круглой формы ;)) и применяем размытие по Гауссу с радиусом, который нам выдаст функция Wand-mode-redeye-blur-radius.

С чего начать

Неплохим началом в ковырянии алгоритма может стать функция Wand-mode-redeye-blur-radius. Это должна быть такая хитрая функция, которая даёт небольшие значения для маленьких входных и очень медленно растёт. Сейчас это убогая WH16 , где W — ширина выделенной области, а H — высота.

Тестирование улучшенного алгоритма

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

(defun Wand-test-redeye (arg)
  "Apply redeye reduction algorithm to ARG's region."
  (interactive "p")
  (let ((pp '(((41 41 349 302) (39 34 86 307)
               (44 42 354 58) (47 42 94 151))      ; first region
              ((29 31 315 676) (36 33 108 660)
               (86 79 168 468))                    ; second region
              ((28 26 335 885) (26 25 147 930)
               (21 18 267 774) (21 18 129 792))    ; third region
              ((26 26 343 1219) (28 29 87 1161)
               (25 18 294 1044) (23 18 127 1036))  ; fourth region
              ((17 15 431 1468) (18 17 376 1463)
               (17 20 310 1468) (17 20 232 1477)
               (13 15 116 1458) (13 15 25 1466)
               (23 25 253 1336) (21 25 132 1378))  ; fifth region
              )))
    (mapc (lambda (reg)
            (Wand-operation-apply 'redeye-remove image-wand reg))
          (nth (1- arg) pp))
    (Wand-redisplay))) 

Откройте картинку redeye-samples.jpg с помощью M-x Wand-display RET. Тестировать алгоритм устранения красных глаз можно с помощью команды C-u <NUM> M-x Wand-test-redeye RET, где <NUM> это номер области над которой нужно произвести тестирование. Как видно из кода всего есть 5 областей. После выполнения команды проверьте результаты с помощью программы для увеличения, я предпочитаю Lupe — быстро работает и вообще отличный софт. Если для всех областей результаты схожи с тем что справа, то у вас неплохо получилось.

Присылайте результаты в список рассылки sxemacs-devel@, для лучшего варианта предусмотрен приз ;)

ReST source Скачать оригинал

среда, 10 декабря 2008 г.

Логотип SXEmacs

Удивительно, но у SXEmacs нет логотипа в формате векторной графики, а тот, что есть в растре, по части качества просто отвратительный.

Я попробовал тут свои силы в создании лого в Inkscape. Вот, что из этого вышло:

Лого

и логотип для бета версии

Бета Лого

Вы можете также скачать исходный sxemacs.svg

Надеюсь Стиву понравится и оный станет официальным логотипом SXEmacs.

ReST source Скачать оригинал

вторник, 9 декабря 2008 г.

SXEmacs - Unix-way

Bloated software

Часто приходится слышать, что Емакс это не Unix-way, но так как он огромен, то в Емакс можно всё, иногда даже говорят, что Емакс это целая операционная система.

Да, GNU Emacs огромен, ибо подход выбран доставлять до пользователя максимально укомплектованный дистрибутив. В SXEmacs выбран другой подход — предоставить пользователю небольшой дистрибутив и лёгкий способ установки пакетов. Напомню, что в SXEmacs (в отличии от XEmacs), при поддержке ffi-curl, можно начать устанавливать пакеты без наличия каких-либо предустановленных пакетов. Так каким образом в дистрибутиве размером 9Mb может быть всё? Его там нет. Зато в Unix-like системе есть всё.

Вернёмся к изначальному вопросу. Емакс это и есть Unix-way в чистом виде и в Емакс можно всё потому и только потому, что в Unix-like системе можно всё, то есть Емакс это добротный унифицированный пользовательский интерфейс к Unix-like системе.

Давеча, экспериментируя с отображением видео, я добился таки показа видео в обычном glyph посредством xlib-ffi, но далеко не был удовлетворён. Я нутром чувствовал неправильность, хотя результаты были просто потрясающими. А смущало меня наличие mplayer — отличного средства для проигрывания видео. Памятуя совет из SICPмысли отталкиваясь от желаемого (wishful thinking)1, я всё мечтал, чтобы видео в glyphе отображал mplayer, а не сам SXEmacs. И в итоге додумал как это можно сделать! Этот способ работает как в SXEmacs так и в XEmacs, хотя в XEmacs есть небольшие ограничения. Итак, код:

(defun lg-video-glyph (file &optional w h)
  "Create video glyph to play FILE.
Optionally you can specify width and height by passing W and H args."
  (let* ((gg (make-glyph [subwindow]))
         (prc (start-process "mplayer" nil "mplayer"
                             "-wid" (int-to-string
                                     (subwindow-xid
                                      (glyph-image-instance gg)))
                             (expand-file-name file))))
    (resize-subwindow
     (glyph-image-instance gg) (or w 200) (or h 140))
    (put gg 'mplayer-proc prc)
    gg))

(setq gg (lg-video-glyph "~/video/kids/kung-fu-panda.mp4"))
(set-extent-end-glyph
 (make-extent (point) (point))
 gg) 

конечно, укажите не ~/video/kids/kung-fu-panda.mp4, а реальный какой-нибудь файл с видео, который у вас есть. Появившийся, после выполнения s-exp с set-extent-end-glyph, glyph можно изменять в размерах при помощи:

(resize-subwindow (glyph-image-instance gg) WIDTH HEIGHT) 

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

В XEmacs вы можете обнаружить, что glyph становится микроскопическим (20x20) если изменить размер фрейма XEmacs. Эта неприятность была исправлена в SXEmacs.

Есть и ещё проблема. Как нам определить размер видео фрейма, чтобы выставить размеры glyphа, соблюдающие пропорцию? Можно, конечно, разобрать вывод программы mplayer, но что если формат вывода будет изменён2? Нет, нужно что-то более подходящее. В SXEmacs есть такая вещь как media-stream, воспользуемся этой возможностью:

(make-media-stream :file "~/crypt/video/kung-fu-panda.mp4" 'ffmpeg)
=>
#<media-stream :kind #<file "/home/lg/crypt/video/kung-fu-panda.mp4">
  #<media-substream :type #<video mov,mp4,m4a,3gp,3g2,mj2
                            (h264), 720x304 (2.37/1)>>
  #<media-substream :type #<audio mov,mp4,m4a,3gp,3g2,mj2
                            (mpeg4aac), stereo, 48000 Hz, 16 Bit>>
  driven by ffmpeg> 

как видим, информация о размере фрейма у нас имеется, в моём случае это 720x304.

У многих может возникнуть резонный вопрос «зачем в SXEmacs отображать видео?», некоторые даже могут подумать, что это идиотизм. Но представим, что есть «сумасшедший», который решил написать клиента для обмена сообщениями, работающего под SXEmacs. Причём сообщением может быть не только коротенький текст, но и голос или видео+голос или даже объект векторной графики. mplayer умеет принимать данные из stdin, поэтому вырисовывается неплохая схема медиа потоков.

SXEmacs media streams

Схема медиа потоков в SXEmacs

Опять же может возникнуть вопрос зачем такой клиент в SXEmacs, ведь есть XXX. Да, как отдельный компонент, возможно, задающие вопрос правы, но если этот клиент будет частью системы для совместной разработки (collaborative development), то уже не так очевидно. Понятно, что в системе совместной разработки должны присутствовать компоненты:

  • Мощный текстовый редактор ;)
  • Средство для совместного редактирования файлов
  • Средство для обмена сообщениями: текстовыми, голосовыми, видео
  • Средства для мозгового штурма — совместная доска для рисования, писания, черкания и т.д.
  • Система контроля версий

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

Основной же вывод из примера с видео-glyph я хочу сделать, что SXEmacs — это и есть Unix-way как он задумывался изначально.


1В русском переводе SICP «wishful thinking» переведено как «мечтать не вредно».
2Стандартизированный ввод и вывод программ это тоже один из аспектов Unix-way.

ReST source Скачать оригинал

пятница, 5 декабря 2008 г.

Магические файлы

Расскажу про одну особенность в SXEmacs — find-file-magic-files-alist.

Всегда есть специальные файлы, которые не нужно открывать как текстовые, а нужно его специальным образом обработать и отобразить какую-нибудь информацию. К таким файлам относятся, например, директории. Я сомневаюсь, что вы хотели бы при выполнении команды C-x C-f /tmp RET увидеть бинарное содержимое файла /tmp (напомню, что директории это такие же файлы как и все остальные). Вы наверняка хотели бы, чтобы запустился dired над этой директорией и вы увидели бы её содержимое. В GNU Emacs и XEmacs это сделано явно, то есть при открытие файла (вызове find-file-noselect, а остальное семейство find-file-функций использует эту) происходит проверка, является ли файл директорией и если да, то запускается dired.

Этот подход очень ограниченный, так как не только директории могут быть магическими файлами. Поэтому в SXEmacs была введена новая переменная — find-file-magic-files-alist. Это обычный alist (список пар), в котором голова пары это предикат получающий имя файла и возвращающий non-nil в случае если файл магический, а хвост пары это одноарная функция, получающая имя файла (про который уже известно, что он магический) и обязательно возвращающая какой-нибудь буфер, в котором содержится интерпретация этого магического файла.

По умолчанию в find-file-magic-files-alist всего одна пара — (file-directory-p . find-file-try-dired-noselect). Как вы догадались это для работы dired.

Мы уже знаем про мощный инструмент работы с изображениями — Wand-mode. Хотелось бы сделать так, что если какой-нибудь файл может быть открыт с помощью Wand-display, то и использовать его для таких файлов. Скажем, что файлы с изображениями — магические и воспользуемся переменной find-file-magic-files-alist:

(push '(Wand-file-supported-for-read-p . Wand-display-noselect)
      find-file-magic-files-alist) 

или и того проще:

(Wand-find-file-enable) 

теперь если мы откроем файл с картинкой с помощью C-x C-f (или любой другой командой find-file-семейства), то у нас запуститься Wand-mode.

Заметим, что предикаты в find-file-magic-files-alist должны быть очень точными. Если вдруг какой-нибудь предикат вернёт non-nil для немагического файла - это приведёт к проблемам. На счастье в SXEmacs есть команда-функция magic:file-type, которая воспользуется прелестями libmagic и вернёт вероятный тип файла. Наверняка вы знаете программу file(1), которая использует libmagic.

Ещё один пример магический файлов. Это файлы с базами SQLite. Я уж точно не хочу открыв, с помощью C-x C-f, файл с базой увидеть её бинарное содержимое. Заместо хотелось бы, что бы запустилась программа sqlite, натравленная на этот файл, и я бы мог давать всякие команды, SQLные ли запросы или всякие .tables и т.д. Напишем пару функций - предикат и открывалку:

(defun lg-sqlite-file-p (filename)
  "Return non-nil if FILENAME is actually SQLite format 3 file."
  (with-temp-buffer
    (insert-file-contents-literally filename nil 0 15)
    (string= (buffer-substring) "SQLite format 3")))

(defun lg-sqlite-find-file (file)
  (require 'sql)
  (setq sql-database file)
  (funcall (sql-product-feature :sqli-connect 'sqlite))
  (setq sql-interactive-product 'sqlite)
  (setq sql-buffer (current-buffer))
  (sql-interactive-mode)
  (rename-buffer (format "*SQL:%s*" (file-name-nondirectory file)))
  (current-buffer)) 

Если у вас есть рабочий ffi-sqlite, то можете воспользоваться уже готовым предикатом - sqlite-file-p. Теперь добавим элемент в find-file-magic-files-alist:

(push '(lg-sqlite-file-p . lg-sqlite-find-file)
      find-file-magic-files-alist)

Всё, теперь можно открывать файлы с базами SQLiteC-x C-f ~/test.db RET.

Аналогичным образом можно сделать, например, чтобы запускался gdb-with-core с правильными аргументами при открытии core файлов.

Вообще, Емаксу необходим более продвинутый механизм автоматического определения, что делать с файлом на замену давно устаревшего auto-mode-alist. find-file-magic-files-alist может стать неплохим началом в создании такого механизма.

ReST source Скачать оригинал

вторник, 2 декабря 2008 г.

Wand-mode -- забудьте о display(1)

Wand-mode это режим просмотра и редактирования изображений для SXEmacs. Wand-mode базируется на API ImageMagick и поэтому в Wand-mode возможны многие операции программ convert(1), display(1) и т.д. Wand-mode поддерживает все форматы поддерживаемые IM, а их более 100, в том числе PDF, EPS, etc.

Знакомство с Wand-mode

M-x Wand-display RET /path/to/image.jpg RET получаем результат примерно следующий:
Wand-display sample
Смотрим информацию о файле - кнопка i:
Wand-display identify
Теперь подредактируем: наложим операцию очищения от мусора (Despeckle) и немного уменьшим контрастность. Используем кнопки o despeckle RET далее o contrast RET decrease RET. Везде будет работать completition так что не переживайте. Получаем результат:
Wand-display edit

Просмотр директорий с изображениями

У Wand-mode простые, но неплохие навигационные возможности для просмотра. Откройте любой файл с помощью Wand-display, если в его директории есть ещё файлы, поддерживаемые IM, то вы можете использовать кнопки:
  • Space - следующая картинка
  • BackSpace - предыдущая
  • M-< - Самая первая картинка в директории
  • M-> - Самая последняя картинка в директории

Меню Wand-mode

У Wand-mode есть отличное меню (чем-то схожее с меню display(1)) для работы изображением.

Wand-display menu
Меню доступно либо из menubar если он у вас включён, либо же по правой кнопке мыши. Все команды, которые можно выполнить клавиатурой можно выполнить и через меню. Верно и обратное - все команды, которые можно выполнить через меню можно выполнить и через клавиатуру.

Настройка Wand-mode

У Wand-mode не так много параметров, поэтому настройка дело простое. Как обычно вы можете всё сделать через M-x customize-group RET Wand-mode RET, я лишь приведу небольшое описание параметров, которые вы скорее всего захотите изменить:
  • Wand-mode-zoom-factor - Number (default: 2)

    Как из названия ясно это коэффициент для команд +(zoom-in) и - (zoom-out)

  • Wand-mode-show-fileinfo - Boolean (default: t)

    Если non-nil, то будет выводится минимальная информация о просматриваемом файле. Для более подробной информации, как уже отмечалось, есть команда i.

  • Wand-mode-show-operations - Boolean (default: t)

    Если non-nil, то при выполнении операций будет выводится список операций над изображением. Выполнять команды undo/redo вы сможете в любом случае.

  • Wand-mode-auto-fit - Boolean (default: t)

    Если non-nil, то при выводе изображения в окне SXEmacs оно(изображение) будет уменьшено если не умещается.

  • Wand-mode-auto-rotate - Boolean (default: t)

    Если non-nil, то в случае наличия у изображения информации о его ориентации (современные фотоаппараты умеют правильно определять ориентацию в зависимости от положения фотоаппарата в пространстве), то выводимое изображение будет автоматически повёрнуто. При этом поворот будет входить в список совершённых операций над изображением. Достаточно удобно.

Внесение изменений и сохранение результатов

Для операций над изображением по умолчанию доступны следующие кнопки:
  • l - Поворот на 90° против часовой стрелки (как бы влево)
  • r - Поворот на 90° по часовой стрелке (как бы вправо)
  • + - Увеличение изображения в соответствии с Wand-mode-zoom-factor
  • - - Уменьшение изображения в соответствии с Wand-mode-zoom-factor
  • o - Главная кнопка операций. Спрашивает у пользователя какую операцию и с какими параметрами применить. Поддерживает completition.
Операции можно применять одна за другой, как мы уже делали. Все операции над картинкой записываются и в любой момент можно сделать undo или redo операций с помощью стандартных клавиш C-/ (или C-_) и C-x C-/, также можно повторно применить последнюю операцию при помощи кнопки C-. или C-x M-: (в обычном режиме эта комбинация выполняет команду `repeat-complex-command'). Чтобы отменить все операции просто перезагрузите изображение при помощи команды C-r.

Теперь когда новое изображение готово, его нужно сохранить на диск. Для этого есть две команды:

  • C-x C-w - Вам предложат выбрать один из форматов для записи и имя нового файла. ВНИМАНИЕ: запись в неграфические форматы типа HTML, TXT и т.д. может привести к core dump. По всей видимости это связано с ошибкой в IM, но, к сожалению, разработчики IM не шибко контактные ребята, поэтому бог знает когда это будет исправлено.
  • C-x C-s - Простой вариант записи. Вам предложат выбрать имя файла, а формат изображения будет определён из расширения имени.

Операции над областью изображения

С помощью левой кнопки мыши можно выделить некоторую область изображения, например руку Сары:
Wand-display region
Если параметр Wand-mode-show-fileinfo проставлен в non-nil, то вы увидите координаты области, иногда это полезно. Когда область активна, то некоторые операции (которые можно применить для области, например изменение контраста, уменьшение шума, размытие и т.д.) будут изменять изображение только в области. Есть также команды, которые можно выполнить только для области, такие как:
  • c - crop, оставить только область и отрезать всё что вне её
  • o chop RET - chop, вырезать область и сомкнуть края
  • Устранение красных глаз
Выполнив операцию над областью, выделение исчезает. Чтобы заново выделить последнюю область используйте команду C-M-z (в обычном режиме эта комбинация клавиш выполняет activate-region).

Устранение красных глаз

Самое главное в устранении красных глаз это грамотно выделить область с краснотою. Выделять лучше всего таким образом, чтобы краснота была в центре и оставалось немного пространства от красноты до краёв выделенной области. Вот пример удачного выделения:
Wand-display red eye good region
А вот не совсем удачного, хотя алгоритм устранения красных глаз сработает и в данном случае, но при более тяжёлых будут заметны артефакты:
Wand-display red eye bad region
После того как вы выделили нужную область просто нажмите . или выберите пункт в Меню Wand →Region→Remove red eye. Алгоритм устранения красных глаз достаточно прямолинеен и не дотягивает до реализованных в профессиональных утилитах. С другой стороны результаты демонстрирует неплохие и пригодные для сельской местности. Я провёл сравнение и вот результаты:
Wand-display red eyes sxemacs vs prof
Про то как улучшить алгоритм устранения красных глаз и как его тестировать я напишу в следующий раз.

Сводная таблица операций

Ну и напоследок я сделал небольшой обзор операций над изображениями. Заметим, что операции, которые не изменяют размер изображения могут без проблем применяться к области:
Transform
Sample Width: 400 Height: 200
Rotate Degrees: 120°
Flip
Flop
Effect
Add Noise Noise type: Poisson
Despeckle
Edge detect Radius: 1.0
Emboss Radius: 1.0 Sigma: 2.0
Gaussian Blur Radius: 1.0 Sigma: 2.0
Radial Blur Radius: 3.0
Reduce Noise
Sharpen Radius: 1.0 Sigma: 2.0
Spread Radius: 1.0
Enhance
Contrast Type: increase
Contrast Type: decrease
Enhance
Equalize
Negate
Normalize
F/X
Charcoal Radius: 1.0 Sigma: 2.0
Implode Radius: 0.4
Implode Radius: -0.4
Oil Paint Radius: 2.0
Sepia Tone Threshold: 85%
Solarize Threshold: 50%
Swirl Angle: 90°
Vignette Black/White: 10
Wave Amplitude: 2 Wave length: 10