Java, UX, HTML, CSS, WEB-design

Как оптимизировать изображения с помощью холста HTML5

Краткое описание по статье Как оптимизировать изображения с помощью холста HTML5

Название: Как оптимизировать изображения с помощью холста HTML5 . Краткое описание: [ad_1] ⭐ Сергей . Дата публикации: 20.02.2022 . Автор: Алишер Валеев .

Для чего создан сайт Novosti-Nedeli.ru

Данный сайт посвящен новостям мира и мира технологий . Также тут вы найдете руководства по различным девайсам.

Сколько лет сайту?

Возраст составляет 3 года

[ad_1]

  • Сергей Чикуёнок

  • 0 Комментарии

Как оптимизировать изображения с помощью холста HTML5

  • 14 минут чтения

  • Кодирование, Оптимизация, HTML5, Адаптивные изображения

Краткое резюме ↬

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

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

Оптимизировать такое изображение было бы настоящей головной болью, потому что оно содержит много красного (который вызывает больше артефактов в JPEG) и шума (который создает ужасные артефакты в JPEG и плохо подходит для упаковки PNG). Лучшая оптимизация, которую я смог получить для этого изображения, — это 330 КБ JPEG, что довольно много для одного изображения. Итак, я решил провести несколько экспериментов с улучшением изображения прямо в браузере пользователя.

Дальнейшее чтение на SmashingMag:

  • Вскрытие производительности веб-эффектов для изображений
  • HTML5: факты и мифы
  • Эффективное изменение размера изображения с помощью ImageMagick
  • Умные методы оптимизации JPEG

Еще после прыжка! Продолжить чтение ниже ↓

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

В Photoshop создать монохроматический шум очень просто: просто перейдите к FilterNoiseAdd Noise. Но на исходном изображении шум фактически затемняет некоторые пиксели (т.е. белых пикселей нет). Это ставит новую задачу: применить шумовой слой с режимом наложения «Умножение» на изображение сцены.

Холст HTML5

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

Массив пикселей холста представляет собой простой массив, содержащий данные RGBa каждого пикселя. Вот как выглядит этот массив данных:

pixelData = [pixel1_red, pixel1_green,
pixel1_blue, pixel1_alpha, pixel2_red,
pixel2_green, pixel2_blue, pixel2_alpha, …];

Таким образом, массив данных изображения содержит total_pixels×4 элементы. Например, изображение 200×100 будет содержать 200×100×4 = 80 000 элементов в этом массиве.

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

// Coordinates of image pixel that we will modify
var x = 10, y = 20;

// Create a new canvas
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 100;
document.body.appendChild(canvas);

// Get drawing context
var ctx = canvas.getContext('2d');

// Get image data
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

// Calculate offset for pixel
var offset = (x - 1 + (y - 1) * canvas.width) * 4;

// Set pixel color to opaque orange
imageData.data[offset]     = 255; // red channel
imageData.data[offset + 1] = 127; // green channel
imageData.data[offset + 2] = 0;   // blue channel
imageData.data[offset + 3] = 255; // alpha channel

// Put image data back into canvas
ctx.putImageData(imageData, 0, 0);

Создание шума

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

function addNoise(canvas) {
   var ctx = canvas.getContext('2d');

   // Get canvas pixels
   var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
   var pixels = imageData.data;

   for (var i = 0, il = pixels.length; i < il; i += 4) {
var color = Math.round(Math.random() * 255);

      // Because the noise is monochromatic, we should put the same value in the R, G and B channels
      pixels[i] = pixels[i + 1] = pixels[i + 2] = color;

      // Make sure pixels are opaque
      pixels[i + 3] = 255;
   }

   // Put pixels back into canvas
   ctx.putImageData(imageData, 0, 0);
}

// Set up canvas
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 200;
document.body.appendChild(canvas);

addNoise(canvas);

Результат может выглядеть так:

шум

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

Режимы наложения

Любой, кто работал с Adobe Photoshop или любым другим продвинутым графическим редактором, знает, что такое режимы наложения слоев:

режимы наложения

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

(colorA * colorB) / 255

То есть мы должны умножить два цвета (значение каждого канала) и разделить на 255.

Давайте изменим наш фрагмент кода: загрузим изображение, создадим шум и применим его, используя режим наложения «Умножение»:

// Load image. Waiting for onload event is important
var img = new Image;
img.onload = function() {
addNoise(img);
};
img.src = "https://www.smashingmagazine.com/2011/08/optimize-images-with-html5-canvas/stage-bg.jpg";

function addNoise(img) {
   var canvas = document.createElement('canvas');
   canvas.width = img.width;
   canvas.height = img.height;

   var ctx = canvas.getContext('2d');

   // Draw image on canvas to get its pixel data
   ctx.drawImage(img, 0, 0);

   // Get image pixels
   var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
   var pixels = imageData.data;

   for (var i = 0, il = pixels.length; i < il; i += 4) {
      // generate "noise" pixel
      var color = Math.random() * 255;

      // Apply noise pixel with Multiply blending mode for each color channel
      pixels[i] =     pixels[i] * color / 255;
      pixels[i + 1] = pixels[i + 1] * color / 255;
      pixels[i + 2] = pixels[i + 2] * color / 255;
   }

   ctx.putImageData(imageData, 0, 0);
   document.body.appendChild(canvas);
}

Результат будет выглядеть так:

сценический шум

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

Альфа-композитинг

Процесс объединения двух цветов с прозрачностью называется «Альфа-композитинг». В простейшем случае компоновки алгоритм будет выглядеть так:

colorA * alpha + colorB * (1 - alpha)

Здесь, alpha — коэффициент композиции (прозрачности) от 0 до 1. Выбор цвета фона (colorB) и который будет наложением (colorA) является важным. В этом случае фоном будет изображение штор, а шум — наложением.

Добавим еще один аргумент для addNoise() функция, которая будет управлять альфа-смешиванием и модифицировать основную функцию для соблюдения прозрачности при смешивании изображений:

var img = new Image;
   img.onload = function() {
   addNoise(img, 0.2); // pass 'alpha' argument
};
img.src = "https://www.smashingmagazine.com/2011/08/optimize-images-with-html5-canvas/stage-bg.jpg";

function addNoise(img, alpha) { // new 'alpha' argument
   var canvas = document.createElement('canvas');
   canvas.width = img.width;
   canvas.height = img.height;

   var ctx = canvas.getContext('2d');
   ctx.drawImage(img, 0, 0);

   var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
   var pixels = imageData.data, r, g, b;

   for (var i = 0, il = pixels.length; i < il; i += 4) {
      // generate "noise" pixel
      var color = Math.random() * 255;

      // Calculate the target color in Multiply blending mode without alpha composition
      r = pixels[i] * color / 255;
      g = pixels[i + 1] * color / 255;
      b = pixels[i + 2] * color / 255;

      // alpha compositing
      pixels[i] =     r * alpha + pixels[i] * (1 - alpha);
      pixels[i + 1] = g * alpha + pixels[i + 1] * (1 - alpha);
      pixels[i + 2] = b * alpha + pixels[i + 2] * (1 - alpha);
   }

   ctx.putImageData(imageData, 0, 0);
   document.body.appendChild(canvas);
}

Результат именно тот, что нам нужен: слой шума, примененный к фоновому изображению в режиме наложения Multiply, с прозрачностью 20%:

сценический шум-альфа

Оптимизация

Хотя полученное изображение выглядит идеально, производительность этого скрипта довольно низкая. На моем компьютере это занимает около 300 миллисекунд (мс). На компьютере среднего пользователя это может занять еще больше времени. Итак, мы должны оптимизировать этот скрипт. Половина кода использует вызовы API браузера, такие как создание холста, получение данных изображения и их отправка обратно, поэтому мы мало что можем с этим сделать. Другая половина — это основной цикл для наложения шума на сценическое изображение, и его можно идеально оптимизировать.

Размер нашего тестового изображения составляет 1293×897, что приводит к 1 159 821 итерации цикла. Довольно большое число, поэтому даже небольшие модификации могут привести к значительному повышению производительности.

Например, в этом цикле мы рассчитали 1 - alpha три раза, но это статическое значение. Мы должны определить новую переменную вне for петля:

var alpha1 = 1 - alpha;

И тогда мы заменим все вхождения 1 - alpha с участием alpha1.

Далее, для генерации пикселей шума мы используем Math.random() * 255 формула. Но несколькими строками позже мы делим это значение на 255, поэтому r = pixels[i] * color / 255. Таким образом, нам не нужно умножать и делить; мы просто используем случайное значение. Вот как выглядит основной цикл после этих настроек:

var alpha1 = 1 - alpha;
for (var i = 0, il = pixels.length; i < il; i += 4) {
   // generate "noise" pixel
   var color = Math.random();

   // Calculate the target color in Multiply blending mode without alpha composition
   r = pixels[i] * color;
   g = pixels[i + 1] * color;
   b = pixels[i + 2] * color;

   // Alpha compositing
   pixels[i] =     r * alpha + pixels[i] * alpha1;
   pixels[i + 1] = g * alpha + pixels[i + 1] * alpha1;
   pixels[i + 2] = b * alpha + pixels[i + 2] * alpha1;
}

После этих небольших оптимизаций addNoise() функция работает около 240 мс (увеличение на 20%).

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

var alpha1 = 1 - alpha;
var origR, origG, origB;

for (var i = 0, il = pixels.length; i < il; i += 4) {
   // generate "noise" pixel
   var color = Math.random();

   origR = pixels[i]
   origG = pixels[i + 1];
   origB = pixels[i + 2];

   // Calculate the target color in Multiply blending mode without alpha composition
   r = origR * color;
   g = origG * color;
   b = origG * color;

   // Alpha compositing
   pixels[i] =     r * alpha + origR * alpha1;
   pixels[i + 1] = g * alpha + origG * alpha1;
   pixels[i + 2] = b * alpha + origB * alpha1;
}

Это сокращает время выполнения этой функции до 200 мс.

Экстремальная оптимизация

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

for (var i = 0, il = pixels.length; i < il; i += 4) {
   // generate "noise" pixel
   var color = Math.random();

   origR = pixels[i]

   // Calculate the target color in Multiply blending mode without alpha composition
   r = origR * color;

   // Alpha compositing
   pixels[i] = r * alpha + origR * alpha1;
}

С помощью некоторой школьной алгебры я придумал эту формулу:

for (var i = 0, il = pixels.length; i < il; i += 4) {
   pixels[i] = pixels[i] * (Math.random() * alpha + alpha1);
}

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

То for Цикл содержит только одно простое вычисление, и вы можете подумать, что мы больше ничего не можем сделать. На самом деле, мы можем.

Во время выполнения цикла мы вычисляем случайное значение пикселя и применяем его к исходному. Но нам не нужно вычислять этот случайный пиксель на каждой итерации (помните, у нас их более миллиона!). Скорее, мы можем предварительно вычислить ограниченный набор случайных значений, а затем применить их к исходным пикселям. Это сработает, потому что сгенерированное значение… ну, случайное. Нет повторяющихся шаблонов особых случаев — только случайные данные.

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

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

// Pick the best array length
var rl = Math.round(ctx.canvas.width * 3.73);
var randoms = new Array(rl);

// Pre-calculate random pixels
for (var i = 0; i < rl; i++) {
   randoms[i] = Math.random() * alpha + alpha1;
}

// Apply random pixels
for (var i = 0, il = pixels.length; i < il; i += 4) {
   pixels[i] = pixels[i] * randoms[i % rl];
}

Это сократит время выполнения до 80 мс в браузерах Webkit и значительно ускорит работу в Opera. Кроме того, Opera замедляется в производительности, когда массив данных изображения содержит значения с плавающей запятой, поэтому нам приходится округлять их быстрыми битами. OR оператор.

Окончательный фрагмент кода выглядит так:

var img = new Image;
   img.onload = function() {
   addNoise(img, 0.2); // pass 'alpha' argument
};
img.src = "https://www.smashingmagazine.com/2011/08/optimize-images-with-html5-canvas/stage-bg.jpg";

function addNoise(img, alpha) {
   var canvas = document.createElement('canvas');
   canvas.width = img.width;
   canvas.height = img.height;

   var ctx = canvas.getContext('2d');
   ctx.drawImage(img, 0, 0);

   var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
   var pixels = imageData.data;
   var alpha1 = 1 - alpha;

   // Pick the best array length
   var rl = Math.round(ctx.canvas.width * 3.73);
   var randoms = new Array(rl);

   // Pre-calculate random pixels
   for (var i = 0; i < rl; i++) {
      randoms[i] = Math.random() * alpha + alpha1;
   }

   // Apply random pixels
   for (var i = 0, il = pixels.length; i < il; i += 4) {
      pixels[i] =  (pixels[i] * randoms[i % rl]) | 0;
   }

   ctx.putImageData(imageData, 0, 0);
   document.body.appendChild(canvas);
}

Этот код выполняется примерно за 80 мс на моем ноутбуке в Safari 5.1 для изображения размером 1293×897, т.е. В самом деле быстро. Вы можете увидеть результат онлайн.

Результат

На мой взгляд, результат неплохой:

  • Размер изображения был уменьшен с 330 КБ до 70 КБ, плюс 1 КБ уменьшенного JavaScript. На самом деле изображение можно сохранить с более низким качеством, потому что шум может скрыть некоторые артефакты JPEG.
  • Это совершенно верно прогрессивное улучшение оптимизация. Пользователи современных браузеров увидят изображение с высокой детализацией, в то время как пользователи более старых браузеров (например, IE 6) или с отключенным JavaScript по-прежнему смогут видеть изображение, но с меньшей детализацией.

Единственным недостатком этого подхода является то, что окончательное изображение должно рассчитываться каждый раз, когда пользователь посещает веб-страницу. В некоторых случаях вы можете сохранить финальное изображение как data:url в localStorage и восстановить его при следующей загрузке страницы. В моем случае размер конечного изображения составляет 1,24 МБ, поэтому я решил не хранить его и вместо этого потратить 5 МБ локального хранилища по умолчанию на более полезные данные.

Режимы наложения игровая площадка

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

Заключение

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

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

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

Дальнейшее чтение

  • Спецификация холста HTML5
  • «Учебное пособие по холсту», Mozilla Developer Network
  • «Альфа-композитинг», Википедия
  • «Режимы наложения», Википедия
  • Список алгоритмов режимов наложения, подобных Photoshop.
Сокрушительная редакция
(аль)



[ad_2]
Source: https://smashingmagazine.com

Заключение

Вы ознакомились с статьей — Как оптимизировать изображения с помощью холста HTML5

Пожалуйста оцените статью, и напишите комментарий.

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован.

Кнопка «Наверх»