Как я могу предотвратить потерю качества и затемнение при объединении двух холстов, которые загружают PDF-файлы в JavaScript?

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

Вот код, который я использую для объединения холстов:

async function combinePages(numPages) {
  console.info("Combining ..."+numPages);

  let canvas1, canvas2; // Declare canvas1 and canvas2 variables here

  // Iterate through each page
  for (let i = 1; i <= numPages; i++) {
  
    let element = document.getElementById(`canvasContainer3_${i}`);
    if (!element) {
      // Create a new canvas container element
      let container = document.createElement("div");
      container.id = `canvasContainer3_${i}`;
      // Append the container to the document body
      document.body.appendChild(container);
    }
    
    // Get the canvas elements for the current page
    canvas1 = document.getElementById('canvas1_'+i);
    canvas2 = document.getElementById('canvas2_'+i);
    
    // Create a new canvas element for the combined page
    let combinedCanvas = "";
    combinedCanvas = document.createElement("canvas");
    combinedCanvas.setAttribute('id', 'canvas3_'+i);
    
    let canvas1Width = canvas1.width;
    let canvas1Height = canvas1.height;
    let canvas2Width = canvas2.width;
    let canvas2Height = canvas2.height;

    // Set the size of the combined canvas
    combinedCanvas.width = Math.max(canvas1Width, canvas2Width);
    combinedCanvas.height = Math.max(canvas1Height, canvas2Height);

    let context = combinedCanvas.getContext("2d");
    
    context.drawImage(canvas1, 0, 0);
    
    // Draw the contents of the second canvas onto the combined canvas
    context.globalCompositeOperation = 'multiply';

    context.drawImage(canvas2, 0, 0);
   
    // Add the combined canvas to the page
    let container = document.getElementById(`canvasContainer3_${i}`);
    container.appendChild(combinedCanvas);

    document.getElementById("wrapper").appendChild(container);

    // Hide canvas1 and canvas2
   //canvas1.style.display = "none";
   // canvas2.style.display = "none";
  }
 
  return 'finish combination';
}

Вот пример PDF, который я использую:

PDF 1 объединяется с PDF 2, затем результат объединяется с PDF 3, а затем этот результат объединяется с PDF 4.

Вот результат, который я получаю:

Белый фон серый, а цвет темный.

У вас случайно нет двух образцов изображений?

obscure 23.04.2023 16:46

Примеры изображений добавлены к вопросу

srecce 24.04.2023 17:23

Значит ли это, что у вас всегда есть только один из этих прямоугольников, который отличается от других документов? Вы заранее знаете, какой это будет прямоугольник? Например, знаете ли вы, что PDF#1 будет содержать верхний левый прямоугольник, PDF#2 — верхний правый и т. д.? Если это так, будет ли достаточно просто использовать drawImage(), чтобы нарисовать необходимую часть поверх конечного результата?

Kaiido 25.04.2023 03:04
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
3
93
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема заключается в используемой вами globalCompositeOperation.

Используя «умножить», пиксели верхнего слоя, как следует из названия, умножаются на соответствующий пиксель нижнего слоя. Таким образом, более темная картина является результатом.

Если вы хотите, чтобы два слоя объединялись без затемнения, используйте другую операцию globalCompositeOperation, такую ​​как по умолчанию «source-over».

например

// Draw the contents of the second canvas onto the combined canvas
context.globalCompositeOperation = 'source-over';

Вы можете увидеть вывод различных операций globalCompositeOperation здесь. https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation

Если фон становится темнее, это, безусловно, означает, что он не полностью прозрачен. "source-over" наверное не то, что им нужно.

Kaiido 24.04.2023 09:45

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

Fraser 24.04.2023 10:03

Это верный момент. Но в таком случае мы голосуем за закрытие вопросов, как требующих более подробной информации, мы не отвечаем, надеясь на лучшее. Из того немногого, что мы знаем, source-over не будет работать, и я сомневаюсь, что любой другой gCO будет работать, поэтому указывать на них по крайней мере так же бессмысленно.

Kaiido 24.04.2023 10:11

Примеры изображений добавлены к вопросу. Source-over не сработает: в результате останется только последняя подпись.

srecce 24.04.2023 17:24
Ответ принят как подходящий

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

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

с полотном Б:

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

Теперь, если мы хотим объединить вышеупомянутые холсты A + B, нам нужно просмотреть пиксельные данные каждого изображения и сравнить их с каждым пикселем «пустого» изображения. Если они разные, берите, если одинаковые, оставьте. Результат этого сравнения должен быть сохранен внутри Uint8ClampedArray размера ширины * высоты холста и умножен на четыре, потому что нам нужно сохранить красный, зеленый, синий и альфа-значение для каждого пикселя.

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

Я сделал быстрый интерактивный пример, иллюстрирующий процесс. Просто используйте раскрывающиеся списки (и кнопку «объединить») для последовательного слияния:

PDF №1 с PDF №2

Результат с PDF №3

Результат с PDF №4

let emptyPDF, emptyPDFImageData;
(async() => {
  let pdfs = ["https://i.stack.imgur.com/bvfme.png", "https://i.stack.imgur.com/x9Cam.png", "https://i.stack.imgur.com/M6V2p.png", "https://i.stack.imgur.com/YWxJF.png"];
  for (let a = 0; a < pdfs.length; a++) {
    document.getElementById("pdf" + (a + 1)).getContext("2d").drawImage(await loadImage(pdfs[a]), 0, 0)
  }

  emptyPDF = document.createElement("canvas");
  emptyPDF.width = 590
  emptyPDF.height = 180
  emptyPDF.getContext("2d").drawImage(await loadImage("https://i.stack.imgur.com/QUT3B.png"), 0, 0);
  emptyPDFImageData = emptyPDF.getContext("2d").getImageData(0, 0, emptyPDF.width, emptyPDF.height).data;



})();

function loadImage(src) {
  return new Promise((resolve, reject) => {
    let img = new Image();
    img.crossOrigin = ""
    img.onload = () => resolve(img);
    img.src = "https://api.codetabs.com/v1/proxy/?quest = " + src;
  });
}

function merge() {
  let canvas = document.getElementById(document.getElementById("sourceA").value);

  let imageDataA = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height).data;

  canvas = document.getElementById(document.getElementById("sourceB").value);
  let imageDataB = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height).data;

  let colors = new Uint8ClampedArray(new Array(emptyPDF.width * emptyPDF.height * 4).fill(255));
  let colA, colB, colC;
  for (let a = 0; a < colors.length; a += 4) {
    colA = (emptyPDFImageData[a] << 16 | emptyPDFImageData[a + 1] << 8 | emptyPDFImageData[a + 2]);
    colB = (imageDataA[a] << 16 | imageDataA[a + 1] << 8 | imageDataA[a + 2]);
    colC = (imageDataB[a] << 16 | imageDataB[a + 1] << 8 | imageDataB[a + 2]);
    if (colA != colB) {
      colors[a] = imageDataA[a];
      colors[a + 1] = imageDataA[a + 1];
      colors[a + 2] = imageDataA[a + 2];
    } else if (colC != colA) {
      colors[a] = imageDataB[a];
      colors[a + 1] = imageDataB[a + 1];
      colors[a + 2] = imageDataB[a + 2];
    } else {
      colors[a] = emptyPDFImageData[a];
      colors[a + 1] = emptyPDFImageData[a + 1];
      colors[a + 2] = emptyPDFImageData[a + 2];
    }

  }

  let imageData = new ImageData(colors, emptyPDF.width, emptyPDF.height);
  document.getElementById("result").getContext("2d").putImageData(imageData, 0, 0);
}
.container {
  display: flex;
  align-items: center;
}

canvas {
  width: 295px;
}
<div>
  <div class = "container"><canvas id = "pdf1" width = "590" height = "180"></canvas><span>PDF#1</span></div>
  <hr>
  <div class = "container"><canvas id = "pdf2" width = "590" height = "180"></canvas><span>PDF#2</span></div>
  <hr>
  <div class = "container"><canvas id = "pdf3" width = "590" height = "180"></canvas><span>PDF#3</span></div>
  <hr>
  <div class = "container"><canvas id = "pdf4" width = "590" height = "180"></canvas><span>PDF#4</span></div>
  <hr>
  <div class = "container"><canvas id = "result" width = "590" height = "180"></canvas><span>Result</span></div>
</div>
<label>Source A:</label>
<select id = "sourceA">
  <option value = "pdf1">PDF#1</option>
  <option value = "pdf2">PDF#2</option>
  <option value = "pdf3">PDF#3</option>
  <option value = "pdf4">PDF#4</option>
  <option value = "result">Result</option>
</select>
<span>+</span>
<label>Source B:</label>
<select id = "sourceB">
  <option value = "pdf1">PDF#1</option>
  <option value = "pdf2">PDF#2</option>
  <option value = "pdf3">PDF#3</option>
  <option value = "pdf4">PDF#4</option>
  <option value = "result">Result</option>
</select>
<button id = "mergeButton" onclick = "merge()">Merge</button>

Просто примечание: посредник Array не нужен, у вашего Uint8ClampedArray тоже есть метод fill().

Kaiido 25.04.2023 03:01

Другие вопросы по теме