Мультимедиа
Некоторые советы и секреты
Работа с графическими элементами HTML5 довольно проста, но знание некоторых подробностей может помочь в работе с ними в приложениях для Магазина Windows.
Элементы Img
- Используйте атрибут title элемента img для реализации всплывающей подсказки, а не атрибут alt. так же вы можете использовать элемент управления WInJS.UI.Tooltip, как показано в лекции 4 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript".
- Для создания изображений из потока, расположенного в памяти, воспользуйтесь методом MSApp.createBlobFromRandomAccessStream (http://msdn.microsoft.com/library/windows/apps/Hh767329.aspx), результат его работы затем можно передать в URL.createObjectURL для создания подходящего для атрибута src URL. Мы встретимся с этим в данной лекции, и нам это понадобится при работе с контрактом "Общий доступ" в лекции 1 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой". Та же техника работает для аудио и видео-потоков.
- Загружая изображение из http:// или с другого удаленного источника, вы рискуете столкнуться с отображением в элементе изображения-заполнителя с красным значком "X". Для того, чтобы предотвратить это, перехватите событие img.onerror и предоставьте собственный элемент-заполнитель:
var myImage = document.getElementById('image'); myImage.onerror = function () { onImageError(this);} function onImageError(source) { source.src = "placeholder.png"; source.onerror = ""; }
Элементы SVG
- Теги <script> не поддерживаются внутри <svg> .
- Если у вас есть SVG-файл, вы можете загрузить его в элемент img, сославшись на файл в атрибуте src, но это не позволит вам обходить SVG в DOM. Если вам нужно подобное поведение, загрузите SVG в iframe. Содержимое SVG тогда будет расположено внутри свойства contentDocument.documentElement:
<!-- В HTML--> <iframe id="Mysvg" src="myFolder/mySVGFile.svg" /> // В JavaScript var svg = document.getElementById("Mysvg").contentDocument.documentElement;
- Файлы в форматах PNG и JPEG обычно обрабатываются быстрее, чем SVG, поэтому, если технической необходимости в использовании SVG нет, или если вы имеете дело со сценарием, требующем высокой производительности, рассмотрите вариант использования масштабированных растровых изображений. Или вы можете динамически создавать масштабированные статические изображения из SVG для того, чтобы использовать их позже для более быстрого рендеринга:
<!-- В HTML--> <img id="svg" src="somesvg.svg" style="display: none;" /> <canvas id="canvas" style="display: none;" /> // В JavaScript var c = document.getElementById("canvas").getContext("2d"); c.drawImage(document.getElementById("svg"),0,0); var imageURLToUse = document.getElementById("canvas").toDataURL();
- Вот два полезных материала по SVG: http://www.carto.net/papers/svg/samples/ и http://srufaculty.sru.edu/david.dailey/svg/.
Элементы Canvas
Все упомянутые здесь имена фукнций – это методы объекта контекста canvas:
- Помните, что элемент canvas нуждается в задании атрибутов width и height (в JavaScript – canvas.width и canvas.height), а не стилей. Он не принимает таких единиц измерения, как px, em, % и других.
- Несмотря на имя, метод closePath не является прямым дополнением метода beginPath. Метод beginPath используется для создания нового контура, который может быть обведен линией, сбрасывая существующий контур. Метод closePath, с другой стороны, просто соединяет две конечных точки текущего контура, как если бы вы исполнили для этих точек команду lineTo. Он не сбрасывает контур и не создает новый. Это часто вызывает у программистов путаницу, и поэтому иногда можно видеть окружности, нарисованные с линией, проведенной к их центру!
- Вызов stroke необходим для отрисовки контура, до этого вы можете рассматривать его как карандашный набросок чего-либо, который пока не закрашен тушью. Обратите так же внимание на то, что обведение контура означает вызов beginPath.
- При анимации содержимого canvas, выполнение clearRect для всего элемента и перерисовка каждого кадра обычно легче реализуема, чем очищение множества небольших областей и перерисовывание отдельных частей элемента. Хост-процесс приложения, в итоге, выполняет рендеринг всего элемента, со всем его содержимым, для каждого кадра, в целях управления прозрачностью, поэтому попытка оптимизировать производительность путем очищения небольших областей – это неэффективная стратегия, за исключением случаев, когда вы выполняете небольшое количество вызовов API для каждого кадра.
- API отрисовки элемента canvas реализованы путем конверсии его в соответствующие вызовы Direct2D GPU. При таком подходе фигуры выводятся с автоматическим антиалиасингом. В результате, рисование фигуры, наподобие незаполненной окружности, и рисование той же окружности с заданным фоновым цветом, не приводит к очистке каждого пикселя. Для того, чтобы эффективно очистить пиксели, используйте команду clearRect для области, которая немного больше, чем фигура. Это одна из причин, по которой очистка всего элемента и перерисовка каждого кадра часто оказывается самым простым решением.
- Для того чтобы задать в элементе canvas фоновое изображение (то есть, вам не придется каждый раз его перерисовывать), вы можете использовать свойство canvas.style.backgroundImage с подходящим URI для изображения
- Используйте метод msToBlob (http://msdn.microsoft.com/library/windows/apps/hh465735.aspx) для объекта canvas чтобы получить объект типа Blob (http://msdn.microsoft.com/library/windows/apps/hh453178.aspx) для содержимого элемента.
- При использовании drawImage, вам может понадобиться подождать какое-то время, пока необходимое изображение загрузится с использованием подобного кода:
var img = new Image(); img.onload = function () { myContext.drawImage(myImg, 0, 0); } myImg.src = "myImageFile.png";
- Хотя другие графические API видят окружность как особый случай эллипса (с одними и теми же параметрами радиусов x и y), функция arc элемента canvas работает только с окружностями. К счастью, небольшое использование масштабирования упрощает рисование эллипсов, как показано ниже. Обратите внимание на то, что мы используем команды save и restore, таким образом, команда scale применяется только к arc. Она не воздействует на stroke, который используется из main. Это важно, так как если показатель масштабирования все еще действует, когда вы вызываете stroke, ширина линии будет меняться вместо того, чтобы оставаться постоянной.
var img = new Image(); img.onload = function () { myContext.drawImage(myImg, 0, 0); } myImg.src = "myImageFile.png"; function arcEllipse(ctx, x, y, radiusX, radiusY, startAngle, endAngle, anticlockwise) { //Используем меньший радиус как базу и растягиваем другой var radius = Math.min(radiusX, radiusY); var scaleX = radiusX / radius; var scaleY = radiusY / radius; ctx.save(); ctx.scale(scaleX, scaleY); //Обратите внимание на то, что центральная точка должна учитывать масштабирование ctx.arc(x / scaleX, y / scaleY, radius, startAngle, endAngle, anticlockwise); ctx.restore(); }
- Копируя пиксельные данные из видео, возможно с использованием canvas динамически манипулировать видеоданными (не влияя на источник, конечно). Это полезная техника, даже учитывая то, что она интенсивно использует процессор. По этой причине, однако, такой подход может не работать нормально на маломощных устройствах.
Вот пример покадровой манипуляции с видео, техника, которая отлично освещена в блоге команды разработки Windows, в сообщение "Прямая манипуляция пикселями в canvas"1Кроме того, обратитесь к материалу http://beej.us/blog/data/html5s-canvas-2-pixel/ (http://blogs.windows.com/windows/archive/b/developers/archive/2011/02/15/canvas-direct-pixel-manipulation.aspx). В упражнении к этой лекции VideoEdit, файл default.html содержит в теле элементы video и canvas:
<video id="video1" src="ModelRocket1.mp4" muted style="display: none"></video> <canvas id="canvas1" width="640" height="480"></canvas>
В коде (js/default.js), мы вызываем функцию startVideo из обработчика активации. Эта функция запускает воспроизведение видео и использует requestAnimationFrame для выполнения пиксельной манипуляции для каждого видеокадра:
function startVideo() { video1 = document.getElementById("video1"); canvas1 = document.getElementById("canvas1"); ctx = canvas1.getContext("2d"); video1.play(); requestAnimationFrame(renderVideo); } function renderVideo() { //Копируем кадр из видео в canvas ctx.drawImage(video1, 0, 0, canvas1.width, canvas1.height); //Получаем кадр в виде пиксельных данных var imgData = ctx.getImageData(0, 0, canvas1.width, canvas1.height); var pixels = imgData.data; //Обходим пиксели, обрабатывая их так, как нам нужно var r, g, b; for (var i = 0; i < pixels.length; i += 4) { r = pixels[i + colorOffset.red]; g = pixels[i + colorOffset.green]; b = pixels[i + colorOffset.blue]; // Здесь создается негативное изображение pixels[i + colorOffset.red] = 255 - r; pixels[i + colorOffset.green] = 255 - g; pixels[i + colorOffset.blue] = 255 - b; } //Копируем измененные пиксели в canvas ctx.putImageData(imgData, 0, 0); //Запрашиваем следующий кадр requestAnimationFrame(renderVideo); }
Страница в этом примере содержит скрытый видеоэлемент (style="display: none"), в котором запускается воспроизведение при загрузке страницы (video1.play()). В цикле requestAnimationFrame, текущий кадр из видео копируется в canvas (drawImage) и пиксели кадра копируются (getImageData) в буфер imgData. Затем мы обходим этот буфер и инвертируем цветовую информацию, создавая, таким образом, фотографический негатив изображения (альтернативная формула для перевода изображения в цветовой режим оттенков серого показана в комментариях и опущена выше). Затем мы копируем полученные пиксели обратно в canvas (putImageData), таким образом, когда мы возвращаемся, негатив изображения выводится на экран.
Повторюсь, подобные операции интенсивно используют процессор и обычно не подлежат ускорению с помощью GPU, то есть, могут выполняться не очень хорошо на маломощных устройствах (однако, не забудьте запустить Release build (построении Выпуска) программы за пределами отладчика для оценки производительности). Гораздо лучше будет написать DLL для реализации видеоэффектов, там, где это возможно, как обсуждается в разделе "Применение видеоэффектов" ниже. Тем не менее, об этой технике полезно знать. Что на самом деле происходит, так это то, что вместо рисования каждого кадра с помощью вызовов API, мы просто используем видео в качестве источника данных. Мы можем, если захотим, украсить canvas любым другим способом, прежде чем возвращаться из функции renderVideo. Пример подобного, который мне очень нравится, приведен в материале "Манипуляция видео с использованием canvas" (https://developer.mozilla.org/En/Manipulating_video_using_canvas) на сайте разработчиков Mozilla, которыйди намически делает фоновые пиксели зеленого цвета в видеокадре прозрачными, в итоге, элемент img, расположенный под видео, становится видимым сквозь видео, выступая в роли фона. Тот же подход можно использовать для наложения двух видео, в итоге, то видео, которое будет расположено ниже, станет фоном вместо статического изображения. Опять же, помните о производительности маломощных устройств; вы можете решить предоставить параметр, посредством которого пользователь сможет отключить подобные дополнительные эффекты.