Я пытаюсь объединить два холста, каждый из которых содержит идентичный документ 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.
Вот результат, который я получаю:
Белый фон серый, а цвет темный.
Примеры изображений добавлены к вопросу
Значит ли это, что у вас всегда есть только один из этих прямоугольников, который отличается от других документов? Вы заранее знаете, какой это будет прямоугольник? Например, знаете ли вы, что PDF#1 будет содержать верхний левый прямоугольник, PDF#2 — верхний правый и т. д.? Если это так, будет ли достаточно просто использовать drawImage()
, чтобы нарисовать необходимую часть поверх конечного результата?
Проблема заключается в используемой вами 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"
наверное не то, что им нужно.
Отсюда моя ссылка на различные варианты, чтобы ОП мог выбрать наиболее подходящий. Нет смысла гадать, на что похожи изображения, это чистая догадка, не видя их.
Это верный момент. Но в таком случае мы голосуем за закрытие вопросов, как требующих более подробной информации, мы не отвечаем, надеясь на лучшее. Из того немногого, что мы знаем, source-over
не будет работать, и я сомневаюсь, что любой другой gCO будет работать, поэтому указывать на них по крайней мере так же бессмысленно.
Примеры изображений добавлены к вопросу. Source-over не сработает: в результате останется только последняя подпись.
Как вы сами поняли, на 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()
.
У вас случайно нет двух образцов изображений?