Javascript загрузить изображение в Offscreen Canvas, выполнить преобразование webp - PullRequest
0 голосов
/ 28 апреля 2019

Я недавно использовал canvas для преобразования изображений в webp, используя:

const dataUrl = canvas.toDataURL ('image / webp');

Но это наверняка занимает много времениизображения, например, 400 мс.

Я получил предупреждение от Chrome, так как он блокирует пользовательский интерфейс.

Я хотел бы использовать Offscreen Canvas для выполнения этого преобразования в фоновом режиме.

Но:

1) Я не знаю, какой Offscreen Canvas мне следует использовать: a] новый OffscreenCanvas () b] canvas.transferControlToOffscreen ()

2) Я загружаю локальный URL-адрес изображенияв объекте Image (img.src = url), чтобы получить ширину и высоту локального изображения.Но я не понимаю, как перенести объект Image на внеэкранный Canvas, чтобы можно было сделать в работнике:

ctx.drawImage (img, 0, 0)

Потому что еслиЯ не передаю изображение, работник не знает img.

1 Ответ

1 голос
/ 29 апреля 2019

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


X , Не используйте API холста для выполнения преобразования формата изображения .API Canvas с потерями, что бы вы ни делали, вы потеряете информацию из своего исходного изображения, даже если вы передадите ему изображения без потерь, изображение на холсте не будет таким же, как это исходное изображение.
Если вы передадитеформат уже с потерями, такой как JPEG, он даже добавляет информацию, которой не было в исходном изображении: артефакты сжатия теперь являются частью необработанного растрового изображения, и алгоритм экспорта будет рассматривать их как информацию, которую он должен хранить, делая ваш файл, вероятно, больше, чемФайл JPEG, которым вы его кормили.

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


Теперь, если вы выполняете некоторую обработку этого изображения, вы действительно можете экспортировать результаты.
Но вы, вероятно,здесь не нужен этот веб-работник.
Y .Самое большое время блокировки в вашем описании должно быть синхронным toDataURL() вызовом.
Вместо этой исторической ошибки в API, вы всегда должны использовать асинхронный и, тем не менее, более производительный toBlob() метод.В 99% случаев вам все равно не нужен URL-адрес данных, почти все, что вы хотите сделать с URL-адресом данных, следует делать непосредственно с BLOB-объектом.

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

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

large.onclick = e => process('https://upload.wikimedia.org/wikipedia/commons/c/cf/Black_hole_-_Messier_87.jpg');
medium.onclick = e => process('https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Black_hole_-_Messier_87.jpg/1280px-Black_hole_-_Messier_87.jpg');

function process(url) {
  convertToWebp(url)
    .then(prepareDownload)
    .catch(console.error);
}

async function convertToWebp(url) {
  if(!supportWebpExport())
  console.warn("your browser doesn't support webp export, will default to png");

  let img = await loadImage(url);
  if(typeof window.createImageBitmap === 'function') {
    img = await createImageBitmap(img);
  }
  const ctx = get2DContext(img.width, img.height);

  console.time('only sync part');
  ctx.drawImage(img, 0,0);
  console.timeEnd('only sync part');
  
  return new Promise((res, rej) => {
    ctx.canvas.toBlob( blob => {
      if(!blob) rej(ctx.canvas);
      res(blob);
    }, 'image/webp');
  });
}

// some helpers

function loadImage(url) {
  return new Promise((res, rej) => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.src = url;
    img.onload = e => res(img);
    img.onerror = rej;
  });
}

function get2DContext(width = 300, height=150) {
  return Object.assign(
    document.createElement('canvas'),
    {width, height}
  ).getContext('2d');
}

function prepareDownload(blob) {
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = 'image.' + blob.type.replace('image/', '');
  a.textContent = 'download';
  document.body.append(a);
}

function supportWebpExport() {
  return get2DContext(1,1).canvas
    .toDataURL('image/webp')
    .indexOf('image/webp') > -1;
}
<button id="large">convert large image (7,416 × 4,320 pixels)</button>
<button id="medium">convert medium image (1,280 × 746 pixels)</button>

Z .Чтобы нарисовать изображение на OffscreenCanvas из Web Worker, вам понадобится createImageBitmap, упомянутый выше.Действительно, объект ImageBitmap, созданный этим методом, является единственным источником изображения значением, которое могут принимать drawImage () и texImage2D () (*), доступным вРабочие (все остальные элементы DOM).

Этот ImageBitmap является передаваемым , так что вы можете сгенерировать его из основного потока и затем отправить его вам Worker без затрат памяти:

main.js

const img = new Image();
img.onload = e => {
  createImageBitmap(img).then(bmp => {
    // transfer it to your worker
    worker.postMessage({
      image: bmp // the key to retrieve it in `event.data`
    },
   [bmp] // transfer it
  );
};
img.src = url;

Другое решение заключается в получении данных вашего изображения непосредственно от Worker и создании объекта ImageBitmap из извлеченного BLOB-объекта:

worker.js

const blob = await fetch(url).then(r => r.blob());
const img = await createImageBitmap(blob);
ctx.drawImage(img,0,0);

И обратите внимание, если вы получили исходное изображение на главной странице в виде BLOB-объекта (например, из), то даже не идите ни по пути HTMLImageElement, ни по извлечению, напрямую отправьте этот BLOB-объект и сгенерируйте из него ImageBitmap.

* texImage2D фактически принимает большеформаты исходного изображения, такие как TypedArrays и объекты ImageData, но эти TypedArrays должны представлять данные пикселей, как это делают ImageData, и для того, чтобы получить эти данные пикселей, вам, вероятно, нужно уже где-то нарисовать изображение, используя один издругие источники изображений форматы.

...