Содержание

WebGL2Fundamentals.org

Fix, Fork, Contribute

WebGL2 - Cross Origin Images

Эта статья является одной из серии статей о WebGL. Если вы не читали их, я предлагаю начать с более раннего урока.

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

Обычно мы загружаем изображение примерно так

// создает информацию о текстуре { width: w, height: h, texture: tex }
// Текстура начнется с 1x1 пикселей и будет обновлена
// когда изображение загрузится
function loadImageAndCreateTextureInfo(url) {
  var tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  // Заполняем текстуру 1x1 синим пикселем.
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
                new Uint8Array([0, 0, 255, 255]));

  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

  var textureInfo = {
    width: 1,   // мы не знаем размер, пока он не загрузится
    height: 1,
    texture: tex,
  };
  var img = new Image();
  img.addEventListener('load', function() {
    textureInfo.width = img.width;
    textureInfo.height = img.height;

    gl.bindTexture(gl.TEXTURE_2D, textureInfo.texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
    gl.generateMipmap(gl.TEXTURE_2D);
  });
  img.src = url;

  return textureInfo;
}

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

Простое использование <img src="private.jpg"> не является проблемой, потому что хотя изображение будет отображаться браузером, скрипт не может увидеть данные внутри изображения. Canvas2D API имеет способ увидеть внутри изображения. Сначала вы рисуете изображение в canvas

ctx.drawImage(someImg, 0, 0);

Затем вы получаете данные

var data = ctx.getImageData(0, 0, width, height);

Но если изображение, которое вы нарисовали, пришло с другого домена, браузер пометит canvas как загрязненный и вы получите ошибку безопасности при вызове ctx.getImageData

WebGL должен сделать это еще на шаг дальше. В WebGL gl.readPixels - это эквивалентный вызов ctx.getImageData, поэтому вы могли бы подумать, что может быть просто блокировка этого будет достаточной, но оказывается, что даже если вы не можете читать пиксели напрямую, вы можете создавать шейдеры, которые работают дольше на основе цветов в изображении. Используя эту информацию вы можете использовать тайминг для эффективного просмотра внутри изображения косвенно и выяснить его содержимое.

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

Как мы обходим это?

Введите CORS

CORS = Cross Origin Resource Sharing. Это способ для веб-страницы попросить сервер изображения разрешения использовать изображение.

Для этого мы устанавливаем атрибут crossOrigin в что-то, и затем когда браузер пытается получить изображение с сервера, если это не тот же домен, браузер попросит разрешение CORS.

...
+    img.crossOrigin = "";   // просим разрешение CORS
    img.src = url;

Строка, которую вы устанавливаете в crossOrigin, отправляется на сервер. Сервер может посмотреть на эту строку и решить, давать ли вам разрешение или нет. Большинство серверов, которые поддерживают CORS, не смотрят на строку, они просто дают разрешение всем. Вот почему установка пустой строки работает. Все, что это означает в данном случае, это “попросить разрешение” против, скажем, img.crossOrigin = "bob" означало бы “попросить разрешение для ‘bob’”.

Почему мы не просто всегда запрашиваем это разрешение? Потому что запрос разрешения требует 2 HTTP запроса, поэтому это медленнее, чем не запрашивать. Если мы знаем, что мы на том же домене, или мы знаем, что не будем использовать изображение ни для чего, кроме тегов img и или canvas2d, то мы не хотим устанавливать crossOrigin, потому что это сделает вещи медленнее.

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

function requestCORSIfNotSameOrigin(img, url) {
  if ((new URL(url, window.location.href)).origin !== window.location.origin) {
    img.crossOrigin = "";
  }
}

И мы можем использовать это так

...
+requestCORSIfNotSameOrigin(img, url);
img.src = url;

Важно отметить, что запрос разрешения НЕ означает, что вам будет предоставлено разрешение. Это зависит от сервера. Github pages дают разрешение, flickr.com дает разрешение, imgur.com дает разрешение, но большинство веб-сайтов не дают.

Заставить Apache предоставить разрешение CORS

Если вы запускаете веб-сайт с apache и у вас установлен плагин mod_rewrite, вы можете предоставить общее разрешение CORS, поместив

    Header set Access-Control-Allow-Origin "*"

В соответствующий файл .htaccess.

Есть предложения или замечания? Создайте issue на GitHub.
comments powered by Disqus