Skip to content

Commit

Permalink
⚡️ Optimize for PDF size by default
Browse files Browse the repository at this point in the history
By moving to JPEG card images, the whole generation process has been
greatly sped up, and the file size of the generated PDF has greatly
reduced.

However, people might want to go with PNGs instead, so let's make this
a setting.

Closes #2.
  • Loading branch information
foosel committed May 16, 2024
1 parent 91ac77d commit 9e0da72
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 8 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 2024-05-16

### ✨ Improvements

- Added a quality setting to the card extraction dialog. Can be used to optimize for file size or image quality. Optimizing for file size will greatly reduce the size of the generated PDF (and the speed of extraction and generation) at the cost of some slight quality reduction on the card images (JPEG vs lossless PNG). However it should usually not matter and thus is now the default. Implements #2.

### 🐛 Bug fixes

- The output PDF will now be removed again before generating a new one.

## 2024-05-15

### ✨ Improvements
Expand Down
16 changes: 9 additions & 7 deletions assets/cardfoldr.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ const rotateImage180 = async (image) => {
ctx.rotate(Math.PI);
ctx.drawImage(img, -img.width / 2, -img.height / 2);

const src = canvas.toDataURL();
const mimeType = image.startsWith("data:image/png") ? "image/png" : "image/jpeg";
const src = canvas.toDataURL(mimeType);
return src;
}

Expand All @@ -298,15 +299,16 @@ const extractCards = async () => {
const marginY = parseFloat(document.getElementById('marginY').value);

const backLoc = document.getElementById('backs').value;

const rotateBacks = document.getElementById('rotateBacks').checked;
const optimizeFor = document.getElementById('optimize').value;

const scale = 4;
const orientationClass = (width > height) ? "landscape" : "portrait";

clearCards();

const cardsContainer = document.getElementById('cards');
const mimeType = optimizeFor === "quality" ? "image/png" : "image/jpeg";

let expectedTotal;
if (backLoc === "lastpage") {
Expand Down Expand Up @@ -334,7 +336,7 @@ const extractCards = async () => {
await page.render({ canvasContext: ctx, viewport }).promise;

const cardImage = document.createElement('img');
cardImage.src = canvas.toDataURL();
cardImage.src = canvas.toDataURL(mimeType);
cardImage.className = "front";
cardImage.style = `aspect-ratio: ${width} / ${height}`;

Expand Down Expand Up @@ -386,7 +388,7 @@ const extractCards = async () => {

await backsPage.render({ canvasContext: ctx, viewport }).promise;

const src = rotateBacks ? await rotateImage180(canvas.toDataURL()) : canvas.toDataURL();
const src = rotateBacks ? await rotateImage180(canvas.toDataURL(mimeType)) : canvas.toDataURL(mimeType);

for (let i = 1; i < count; i++) {
const cardImage = document.getElementById(`card-${i}`).getElementsByClassName('back')[0];
Expand All @@ -409,7 +411,7 @@ const extractCards = async () => {
await backPage.render({ canvasContext: ctx, viewport }).promise;

const cardImage = document.getElementById(`card-${backCount}`).getElementsByClassName('back')[0];
cardImage.src = rotateBacks ? await rotateImage180(canvas.toDataURL()) : canvas.toDataURL();
cardImage.src = rotateBacks ? await rotateImage180(canvas.toDataURL(mimeType)) : canvas.toDataURL(mimeType);

backCount++;
}
Expand All @@ -434,7 +436,7 @@ const extractCards = async () => {
await backPage.render({ canvasContext: ctx, viewport }).promise;

const cardImage = document.getElementById(`card-${backCount}`).getElementsByClassName('back')[0];
cardImage.src = rotateBacks ? await rotateImage180(canvas.toDataURL()) : canvas.toDataURL();
cardImage.src = rotateBacks ? await rotateImage180(canvas.toDataURL(mimeType)) : canvas.toDataURL(mimeType);

backCount++;
}
Expand All @@ -453,7 +455,7 @@ const extractCards = async () => {
await backPage.render({ canvasContext: ctx, viewport }).promise;

const cardImage = document.getElementById(`card-${backCount}`).getElementsByClassName('back')[0];
cardImage.src = rotateBacks ? await rotateImage180(canvas.toDataURL()) : canvas.toDataURL();
cardImage.src = rotateBacks ? await rotateImage180(canvas.toDataURL(mimeType)) : canvas.toDataURL(mimeType);

backCount++;
}
Expand Down
6 changes: 5 additions & 1 deletion assets/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,11 @@ if (typeof importScripts === "function") {
const deduplicationLUT = {};
const lookupCard = async (card) => {
if (!deduplicationLUT[card]) {
deduplicationLUT[card] = await pdfDoc.embedPng(card);
if (card.startsWith("data:image/png;base64,")) {
deduplicationLUT[card] = await pdfDoc.embedPng(card);
} else {
deduplicationLUT[card] = await pdfDoc.embedJpg(card);
}
}
return deduplicationLUT[card];
}
Expand Down
8 changes: 8 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ <h2>Step 3: Extract Cards</h2>
</select>
</div>

<div class="pure-control-group">
<label for="optimize">Optimize for:</label>
<select id="optimize">
<option value="filesize">File size</option>
<option value="quality">Image quality</option>
</select>
</div>

<div class="pure-controls">
<input type="checkbox" id="rotateBacks" /> <label for="rotateBacks">Rotate backs 180°</label>
<span class="pure-form-message-inline">Use this if the backs are otherwise upside down</span>
Expand Down

0 comments on commit 9e0da72

Please sign in to comment.