Учебник Cairo

Замечания по поводу перевода присылать sergeyvp@gmail.com

Этот учебник выполнен на основе cairo tutorial for python programmers Michael Urman's. Оригинальные части кода были переведены на C, текст был изменён только, насколько это было необходимо.

Cairo - это мощная библиотека 2d графики. Этот документ знакомит вас с работой cairo и со многими функциями, которые вы будете использовать для создания ваших графических приложений.

Для этого вам необходимо следующее:

  1. Сама библиотека Cairo. Вам понадобится и библиотека, и исходные файлы для разработки. Смотрите Download если этого у вас ещё нет.

  2. Компилятор языка C. FAQ содержит минимальные примеры как превратить код в программу которая даёт желаемый результат.

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

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

Помните: в тексте упоминается cairo_push_group() и cairo_pop_group(). Для них необходима по крайней мере версия 1.2.0.

Оглавление

Модель рисования Cairo

Чтобы понять операции использования cairo, мы сначала углубимся в модель рисования cairo. Есть лишь несколько концепций которые применяются в большинстве разных методов. Сначала я опишу термины: цель назначения, источник, маска, контур, и контекст. Затем я опишу команды языка которые предлагают способы манипулирования терминами и отрисовку графики которую вы хотите создать.

Термины

Язык Cairo несколько абстрактный. Чтобы его конкретизировать я включу диаграммы которые изображают взаимодействие. Первые три термина - три слоя которые вы увидите на диаграммах в этом разделе. Четвёртый термин, контур, относится к центру слоя когда это необходимо. Последний термин, контекст, не отображается.

Цель назначения

Цель назначения это поверхность на которой вы рисуете. Она может быть связана с массивом точек как в этом учебнике, или она может быть связана с SVG или PDF файлом, или с чем то другим. Эта поверхность накапливает элементы графики в процессе их применения, позволяя вам создавать сложную работу как будто рисуя на холсте.

Источник

Источник это "краски" с которыми вы собираетесь работать. Предположим это простой чёрный цвет, но прозрачный для нижних слоёв. В отличие от реальных красок в нём не должно быть единственного цвета; он может быть текстурой или даже предварительно созданной целевой поверхностью. А так же в отличие от реальной краски он может содержать информацию Альфа канала прозрачности.

Маска

Маска наиболее важная часть: она контролирует применение источника к целевой поверхности. Это может быть показано как жёлтый слой в промежутке через который применяется источник. Когда вы применяете команду отрисовки, она отпечатывает источник на целевой поверхности. Везде где есть маска, источник копируется. Везде где маски нет, ничего не происходит.

Контур

Контур - это где то между частью маски и частью контекста. Предположим что это тонкая зелёная линия на слое маски. Она управляется с помощью команд контура, затем используется с помощью команды отрисовки.

Контекст

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

Перед тем как вы сможете рисовать с помощью cairo, вам необходимо создать контекст. Контекст хранится в основном типе данных cairo, называется cairo_t. Когда вы создаёте контекст cairo, он должен быть связан со специфичной поверхностью - например поверхностью изображения если вам нужно создать PNG файл. Существует также тип данных для поверхности, называется cairo_surface_t. Вы можете инициализировать свой контекст cairo, например так:

cairo_surface_t *surface; cairo_t *cr; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 120, 120); cr = cairo_create (surface);

Контекст cairo в этом примере связан с поверхностью изображения размером 120 x 120 и глубиной цвета 32 бит RGB и хранит информацию Альфа канала. Поверхность может быть создана специально для большинства привязок cairo, смотрите детали в руководстве.

Команды

Причина использования cairo в программе заключается в рисовании. Cairo внутренне рисует с помощью одной основной операции рисования: источник и маска свободно размещаются где либо на целевой поверхности. Затем слои совмещаются вместе и краска из источника передаётся на целевую поверхность везде где это позволяет маска. Для этого существуют следующие пять команд рисования, или операций. Они различаются способом конструирования маски.

Обводка

Операция cairo_stroke() применяет виртуальный карандаш вдоль контура. Это позволяет источнику передать через маску тонкую (или толстую) линию вдоль контура, в соответствие с карандашной толщиной линии, стилем точек, и наконечниками линии.

cairo_set_line_width (cr, 0.1); cairo_set_source_rgb (cr, 0, 0, 0); cairo_rectangle (cr, 0.25, 0.25, 0.5, 0.5); cairo_stroke (cr);

Заполнение

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

cairo_set_source_rgb (cr, 0, 0, 0); cairo_rectangle (cr, 0.25, 0.25, 0.5, 0.5); cairo_fill (cr);

Отображение текста / Glyphs

Операция cairo_show_text() формирует маску из текста. cairo_show_text() можно представить как быстрое создание контура с помощью cairo_text_path() и последующее использование cairo_fill() для его перевода. Учтите cairo_show_text() кэширует знаки что очень удобно если вы работаете с большим текстом.

cairo_text_extents_t te; cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); cairo_select_font_face (cr, "Georgia", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size (cr, 1.2); cairo_text_extents (cr, "a", &te); cairo_move_to (cr, 0.5 - te.width / 2 - te.x_bearing, 0.5 - te.height / 2 - te.y_bearing); cairo_show_text (cr, "a");

Краска

Операция cairo_paint() использует маску для перевода источника на целевую поверхность. Некоторые люди рассматривают это как безграничную маску, а некоторые как отсутствие маски; результат одинаковый. Соответствующая операция cairo_paint_with_alpha() тоже позволяет перевести весь источник на поверхность, но она переводит только определённый процент цвета.

cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); cairo_paint_with_alpha (cr, 0.5);

Маска

Операции cairo_mask() и cairo_mask_surface() позволяют перевести соответственно прозрачность/непрозрачность вторичной текстуры источника или поверхности. Когда текстура или поверхность непрозрачные, текущий источник переводится на поверхность. Когда текстура или поверхность прозрачные, ничего не переводится.

cairo_pattern_t *linpat, *radpat; linpat = cairo_pattern_create_linear (0, 0, 1, 1); cairo_pattern_add_color_stop_rgb (linpat, 0, 0, 0.3, 0.8); cairo_pattern_add_color_stop_rgb (linpat, 1, 0, 0.8, 0.3); radpat = cairo_pattern_create_radial (0.5, 0.5, 0.25, 0.5, 0.5, 0.5); cairo_pattern_add_color_stop_rgba (radpat, 0, 0, 0, 0, 1); cairo_pattern_add_color_stop_rgba (radpat, 0.5, 0, 0, 0, 0); cairo_set_source (cr, linpat); cairo_mask (cr, radpat);

Рисование с помощью Cairo

Для создания желаемого изображения, вы должны подготовить контекст для каждой команды рисования. Для использования cairo_stroke() или cairo_fill() вам сначала нужен контур. Для использования cairo_show_text() вы должны расположить ваш текст с помощью его точки вставки. Для использования cairo_mask() вам необходим второй источник текстура или поверхность. А для использования любых операций, включая cairo_paint(), вам необходим первичный источник.

Подготовка и выбор источника

Есть три основных вида источников в cairo: цвета, градиенты и изображения. Цвета являются простейшими; они используют однородный тон и прозрачность для всего источника. Вы можете выбрать это без предварительной подготовки с помощью cairo_set_source_rgb() и cairo_set_source_rgba(). Использование cairo_set_source_rgb (cr, r, g, b) эквивалентно использованию cairo_set_source_rgba (cr, r, g, b, 1.0), а выбранный вами цвет источника используется полностью непрозрачным.

cairo_set_source_rgb (cr, 0, 0, 0); cairo_move_to (cr, 0, 0); cairo_line_to (cr, 1, 1); cairo_move_to (cr, 1, 0); cairo_line_to (cr, 0, 1); cairo_set_line_width (cr, 0.2); cairo_stroke (cr); cairo_rectangle (cr, 0, 0, 0.5, 0.5); cairo_set_source_rgba (cr, 1, 0, 0, 0.80); cairo_fill (cr); cairo_rectangle (cr, 0, 0.5, 0.5, 0.5); cairo_set_source_rgba (cr, 0, 1, 0, 0.60); cairo_fill (cr); cairo_rectangle (cr, 0.5, 0, 0.5, 0.5); cairo_set_source_rgba (cr, 0, 0, 1, 0.40); cairo_fill (cr);

Градиенты описывают прогрессию цветов с помощью установки начала и конца передачи расположения и серии "опорных точек" вдоль контура. Линейные градиенты выстраиваются из двух точек через которые располагаются параллельные линии в определённое начало и конец расположения. Радиальные градиенты также строятся из двух точек, но каждая имеет связанный с ней радиус окружности в каждом определённом начале и конце расположения. Прекращаются или добавляются градиенты с помощью cairo_add_color_stop_rgb() и cairo_add_color_stop_rgba() которые принимают цвет как cairo_set_source_rgb*(), а так же смещение указывающее где располагаются линии между расположениями. Цвета между соседними остановками усредняются в пространстве в виде плавного смешивания. В заключение, поведение передачи расположения можно проконтролировать с помощью cairo_set_extend().

int i, j; cairo_pattern_t *radpat, *linpat; radpat = cairo_pattern_create_radial (0.25, 0.25, 0.1, 0.5, 0.5, 0.5); cairo_pattern_add_color_stop_rgb (radpat, 0, 1.0, 0.8, 0.8); cairo_pattern_add_color_stop_rgb (radpat, 1, 0.9, 0.0, 0.0); for (i=1; i<10; i++) for (j=1; j<10; j++) cairo_rectangle (cr, i/10.0 - 0.04, j/10.0 - 0.04, 0.08, 0.08); cairo_set_source (cr, radpat); cairo_fill (cr); linpat = cairo_pattern_create_linear (0.25, 0.35, 0.75, 0.65); cairo_pattern_add_color_stop_rgba (linpat, 0.00, 1, 1, 1, 0); cairo_pattern_add_color_stop_rgba (linpat, 0.25, 0, 1, 0, 0.5); cairo_pattern_add_color_stop_rgba (linpat, 0.50, 1, 1, 1, 0); cairo_pattern_add_color_stop_rgba (linpat, 0.75, 0, 0, 1, 0.5); cairo_pattern_add_color_stop_rgba (linpat, 1.00, 1, 1, 1, 0); cairo_rectangle (cr, 0.0, 0.0, 1, 1); cairo_set_source (cr, linpat); cairo_fill (cr);

Изображения включают обе поверхности, загруженную из существующего файла с помощью cairo_image_surface_create_from_png() и поверхность созданную внутри cairo как предыдущая целевая поверхность. В версии cairo 1.2, легче всего использовать способ создания предыдущих поверхностей в качестве источника с помощью cairo_push_group() или cairo_pop_group() либо cairo_pop_group_to_source(). Используйте cairo_pop_group_to_source() просто для выбора нового источника, а cairo_pop_group() когда вам нужно сохранить его после чего вы сможете выбрать его снова с помощью cairo_set_source().

Создание контура

Cairo всегда имеет активный контур. Если вы вызвали cairo_stroke() она нарисует контур с вашими настройками линии. Если вы вызвали cairo_fill() она заполнит пространство внутри области обозначенной контуром. Но если контур пуст, оба вызова не изменят вашу целевую поверхность. Почему он бывает пуст? Иногда он начинается таким способом; но чаще после того как cairo_stroke() или cairo_fill() освобождает его для того чтобы вы смогли начать новый контур.

Что если вам нужно выполнить несколько действий связанных с одним контуром? Например нарисовать красный прямоугольник с чёрной окантовкой вы можете заполнив прямоугольный контур красной краской (источником), затем прочертить тот же контур черным источником. Прямоугольный контур легко создаётся множество раз, но многие контуры являются более сложными.

Cairo поддерживает простое повторение контуров с помощью альтернативных вариантов собственных операций. Рисует тоже самое, но не сбрасывает контур. Для обводки, наряду с cairo_stroke() есть cairo_stroke_preserve(); для заливки, cairo_fill_preserve() дополняет cairo_fill(). Даже настройка кадрирования имеет вариант сохранения. В оличие от выбора когда для сохранения вашего контура есть только несколько основных операций.

Перемещение

Cairo использует стиль точка-к-точке при создании контура. Начинает с 1, рисует линию к 2, затем к 3, и так далее. Когда вы начинаете контур, или когда вам нужно начать новый дополнительный контур (sub-path), вам нужна будет контрольная точка 1: без соединений. Для этого используйте cairo_move_to(). Это служит контрольной точкой без создания контура связанного с предыдущей точкой. Есть также вариант относительной координации, cairo_rel_move_to(), которая устанавливает новый указатель на указанном расстоянии контура от текущего указателя. После установки вашей контрольной точки, используйте другие операции которые обновят указанные контрольные точки и соединят их каким либо способом.

cairo_move_to (cr, 0.25, 0.25);

Прямые линии

Либо с помощью абсолютных координат cairo_line_to() (прокладывает контур от указателя до точки), либо относительных координат cairo_rel_line_to() (прокладывает контур от указателя в определённом направлении), контур будет соединён в прямую линию. Новая контрольная точка станет другим концом линии.

cairo_line_to (cr, 0.5, 0.375); cairo_rel_line_to (cr, 0.25, -0.125);

Дуги

Дуги являются частью окружности. В отличие от прямых линий, точка указанная вами находится не на контуре. Вместо этого она расположена по центру окружности из которой формируется контур. Точки начала и конца в окружности должны быть определены, и эти точки соединяются либо по часовой стрелке с помощью cairo_arc() либо против часовой стрелки с помощью cairo_arc_negative(). Если предыдущая контрольная точка не в этой новой кривой, прямая линия добавляется из неё в начало дуги. Контрольная точка затем обновляется для завершения дуги. Существуют только абсолютные версии.

cairo_arc (cr, 0.5, 0.5, 0.25 * sqrt(2), -0.25 * M_PI, 0.25 * M_PI);

Кривые

Кривые в cairo являются кубическими кривыми Безье. Они начинаются в текущей контрольной точке и плавно следуют в направлении двух других точек (не пересекая их) до получения третьей точки. Как у линий, есть абсолютный (cairo_curve_to()) и относительный (cairo_rel_curve_to()) вариант. Помните что относительный вариант определяет все точки относительно предыдущей контрольной точки, а не каждую точку относительно предыдущей точки кривой.

cairo_rel_curve_to (cr, -0.25, -0.125, -0.25, 0.125, -0.5, 0);

Замкнутые контуры

Cairo может также замкнуть контур с помощью прямой линии начинающей текущий дополнительный контур (sub-path). Эта прямая линия может быть полезна для последнего угла многоугольника, но бесполезна непосредственно для формы кривой. Замкнутый контур полностью отличается от открытого контура: он является непрерывным контуром не имеющим ни начала ни конца. Замкнутый контур не имеет ни окончаний ни места для их размещения.

cairo_close_path (cr);

Текст

Наконец текст может быть включен в контур с помощью cairo_text_path(). Пути созданные из текста похожи на любые другие контуры, поддерживающие операции обводки или заполнения. Этот путь закрепляется к текущей контрольной точке, поэтому cairo_move_to() выполняется перед включением текста в контур. Однако в этом случае существуют проблемы эффективности если вы работаете с большим текстом; возможно вы должны предпочесть использование команды cairo_show_text() вместо cairo_text_path() и cairo_fill().

Понимание текста

Для эффективного использования текста вам необходимо знать где он будет проходить. Методы cairo_font_extents() и cairo_text_extents() дают вам эту информацию. Так как эту схему сложно смотреть в небольшом масштабе, я предлагаю взять исходник и увеличить размер изображения до 600. Это покажет соотношение между контрольной точкой (красная точка); предложенной следующей точкой (голубая точка); ограничительной рамкой (голубые пунктирные линии); относительного перемещения (сплошная голубая линия); надстрочным интервалом основной строки и подстрочным интервалом (пунктирные зелёные линии).

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

Соотношение - это перемещение из контрольной точки в верхний левый угол ограничительной рамки. Это нулевое смещение или небольшое положительное значение для x перемещения, но может быть отрицательное x для символов похожих на j как показано; они всегда имеют отрицательное значение для y перемещения. Ширина и высота описывают размер ограничивающей рамки. Дополнительное применение может заключаться в указании контрольной точки для следующих букв. Помните что ограничивающая рамка для предложенного текстового блока может может быть пересечена если соотношение негативное, или намного меньше чем предложенная ширина.

В дополнение к расположению, вам также потребуется определить начертание шрифта, стиль и размер. Стиль и начертание шрифта устанавливаются совместно с помощью cairo_select_font_face(), а размер с помощью cairo_set_font_size(). Если вам нужен больший контроль, попробуйте получить cairo_font_options_t которая определяется с помощью cairo_get_font_options(), а устанавливается с помощью cairo_set_font_options().

Работа с преобразованиями

Преобразования имеют три основных назначения. Первое, они позволяют вам устанавливать систему координат с которой легко работать, имея вывод любого размера. Второе, они позволяют вам создавать вспомогательные функции которые работают вокруг или около (0, 0), но могут применяться повсюду в выводе изображения. Третье, они позволяют вам деформировать изображение, вращая окружность дуги внутри эллиптической дуги, и т.д.. Преобразование является способом связывания двух систем координат. Аппаратно-пространственная система координат связана с поверхностью для рисования и не может изменятья. Система координат пользовательского пространства соответствует пространству по умолчанию, но может быть изменено по указанным выше причинам. Вспомогательные функции cairo_user_to_device() и cairo_user_to_device_distance() подсказывают вам аппаратные-координаты для пользовательских-координат позиции или расстояния. Соответственно cairo_device_to_user() и cairo_device_to_user_distance() подсказывают вам пользовательские-координаты для аппаратных-координат позиции или расстояния. Не забывайте сообщать позиции варианта с отсутствием расстояния, и относительное перемещение или другие расстояния через вариант расстояния.

Я поднял все эти причины при рисовании диаграмм в данном документе. Я рисовал на поверхности либо 120 x 120 либо 600 x 600, используя cairo_scale() для получения рабочего пространства 1.0 x 1.0. Для размещения результата в правой колонке, так как обсуждается в модель рисования cairo, я использовал cairo_translate(). А для добавления перспективы к перекрывающимся слоям, я устанавливал произвольную деформацию с помощью cairo_transform() в cairo_matrix_t.

Для понимания ваших преобразований, читайте их снизу в верх, применяя их к нарисованной вами точке. Чтобы понять какие преобразования сделаны, обдумайте этот процесс в обратном порядке. Например если мне нужна рабочая область 1.0 x 1.0 размером 100 x 100 пикселей по центру поверхности 120 x 120 пикселей, я могу сделать это одним из трёх способов:

  1. cairo_translate (cr, 10, 10); cairo_scale (cr, 100, 100);
  2. cairo_scale (cr, 100, 100); cairo_translate (cr, 0.1, 0.1);
  3. cairo_matrix_t mat; cairo_matrix_init (&mat, 100, 0, 0, 100, 10, 10); cairo_transform (cr, &mat);

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

Будьте осторожны когда пытаетесь рисовать линии в течение преобразования. Даже если вы установили толщину вашей линии в значение фактора масштабирования равным 1, толщина линии устанавливается всегда в пользовательских координатах и не изменяется установками масштабирования. Хотя вы выполняя масштабирование желаете увеличить толщину вашей линии согласно маштабирования. Для определения толщины линии в пикселах используйте cairo_device_to_user_distance() для включения в аппаратное-пространство расстояния (1, 1), например, (0.01, 0.01) расстояние пользовательского пространства. Помните что если ваше преобразование деформирует изображение есть необязательный способ определения толщины однородной линии.

Куда двигаться дальше

Это поверхностный учебник. Он не охватывает все функции в cairo, поэтому для более "продвинутого" изучения особенностей использования, вам необходимо посмотреть что то ещё. Разные примеры на cairographics.org руководства в разных направлениях. Как и везде, есть большая разница между знанием правил инструмента и умением использовать его. В заключительном разделе этого документа приводятся некоторые идеи которые помогут вам устранить часть пробелов.

Подсказки и советы

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

Толщина линии

Когда вы работаете с пропорциональным масштабированием, вы можете просто использовать пикселы для толщины вашей линии. Однако можно легко перевести её с помощью cairo_device_to_user_distance() (если толщина в пикселах равна 1):

double ux=1, uy=1; cairo_device_to_user_distance (cr, &ux, &uy); if (ux < uy) ux = uy; cairo_set_line_width (cr, ux);

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

cairo_set_line_width (cr, 0.1); cairo_save (cr); cairo_scale (cr, 0.5, 1); cairo_arc (cr, 0.5, 0.5, 0.40, 0, 2 * M_PI); cairo_stroke (cr); cairo_translate (cr, 1, 0); cairo_arc (cr, 0.5, 0.5, 0.40, 0, 2 * M_PI); cairo_restore (cr); cairo_stroke (cr);

Выравнивание текста

Когда вы пытаетесь центровать текст буква к букве в разных положениях, вы должны решить как именно его центровать. Например следующий код будет фактически центровать буквы индивидуально, что ведёт к неудовлетворительному результату когда ваши буквы имеют разный размер. (В отличие от множества примеров, здесь я предполагаю рабочее пространство 26 x 1.)

cairo_text_extents_t te; char alphabet[] = "AbCdEfGhIjKlMnOpQrStUvWxYz"; char letter[2]; for (i=0; i < strlen(alphabet); i++) { *letter = '\0'; strncat (letter, alphabet + i, 1); cairo_text_extents (cr, letter, &te); cairo_move_to (cr, i + 0.5 - te.x_bearing - te.width / 2, 0.5 - te.y_bearing - te.height / 2); cairo_show_text (cr, letter); }

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

cairo_font_extents_t fe; cairo_text_extents_t te; char alphabet[] = "AbCdEfGhIjKlMnOpQrStUvWxYz"; char letter[2]; cairo_font_extents (cr, &fe); for (i=0; i < strlen(alphabet); i++) { *letter = '\0'; strncat (letter, alphabet + i, 1); cairo_text_extents (cr, letter, &te); cairo_move_to (cr, i + 0.5 - te.x_bearing - te.width / 2, 0.5 - fe.descent + fe.height / 2); cairo_show_text (cr, letter); }

Copyright © 20052007 Michael Urman