← 返回蜂巢洞察

如何使用JavaScript构建基于浏览器的PDF颜色反转工具

长时间阅读PDF文档会让人感到疲劳,尤其是当文档的背景颜色很鲜艳,或者你在光线较暗的环境中阅读时。 在某些情况下,设计师、开发人员和印刷专业人士可能希望查看文档在颜色反转后的效果,或者为便于访问或审查而准备不同的版本。 PDF颜色反转工具能够实现这一目标——它在保持文档结构不变的前提下,改变PDF页面的颜色。用户无需手动编辑每一张图片或图形,只需上传PDF文件,选择颜色反转模式,指定需要处理的页面范围,调整输出质量,然后点击几下即可生成新的PDF文件并下载。 在本教程中,你将使用JavaScript制作一个基于浏览器的PDF颜色反转工具。用户可以上传PDF文件,预览其内容,选择反转方式,指定处

长时间阅读PDF文档会让人感到疲劳,尤其是当文档的背景颜色很鲜艳,或者你在光线较暗的环境中阅读时。

在某些情况下,设计师、开发人员和印刷专业人士可能希望查看文档在颜色反转后的效果,或者为便于访问或审查而准备不同的版本。

PDF颜色反转工具能够实现这一目标——它在保持文档结构不变的前提下,改变PDF页面的颜色。用户无需手动编辑每一张图片或图形,只需上传PDF文件,选择颜色反转模式,指定需要处理的页面范围,调整输出质量,然后点击几下即可生成新的PDF文件并下载。

在本教程中,你将使用JavaScript制作一个基于浏览器的PDF颜色反转工具。用户可以上传PDF文件,预览其内容,选择反转方式,指定处理范围,调整输出质量,实时查看效果,生成反转后的PDF文件,检查结果,重命名输出文件并下载它——所有这些操作都不需要将文档上传到服务器上。

我们将使用PDF.js在浏览器中显示PDF页面,利用HTML Canvas API来修改像素颜色,再通过PDF-lib生成最终的PDF文件。

完成本教程后,你将会拥有一个功能完备的客户端PDF颜色反转工具,它的效果与我在“All In One Tools”网站上提供的工具类似。

目录

这个PDF颜色反转工具的作用及工作原理

PDF颜色反转工具通过反转PDF页面的颜色来改变文档的外观:浅色会变成深色,深色会变成浅色,所有像素都会被重新计算,从而生成原始文档的反转版本。这种功能在某些环境中可以提高可读性,帮助用户在暗模式下预览设计内容,或者为查看文档提供另一种方式。

在这个项目中,用户可以上传PDF文件,浏览其中的每一页,选择颜色反转的方式,指定需要处理的页面范围,选择输出文件的质量,开启实时预览功能,生成反转后的文档,重新命名输出文件,然后下载最终的PDF文件。由于所有的处理操作都是在浏览器内部进行的,因此原始文档始终不会离开用户的设备。

在后台,PDF.js会将每一页PDF内容渲染到HTML画布上。一旦某页被渲染完成,JavaScript就会通过Canvas API访问这些像素数据,然后重新计算每个像素的红色、绿色和蓝色数值,从而生成该页面的反转版本。

在处理完所有选中的页面后,PDF-lib会将这些修改后的页面合并成一个新的PDF文件,用户可以预览并下载这个新文件。

一个像素由四个数值来表示:

const pixel = {

    red: 120,

    green: 85,

    blue: 200,

    alpha: 255

};

在进行颜色反转操作时,每个颜色通道的值都会被减去255,从而实现颜色的反转。

red = 255 - red;

green = 255 - green;

blue = 255 - blue;

对每一页上所有的像素都重复进行这样的计算,最终就能得到反转后的PDF文件,同时文档的布局、页面顺序和尺寸也会保持不变。

项目设置

在编写任何图像处理代码之前,我们先来为这个PDF颜色反转工具创建一个简单的项目结构吧。

我们将使用纯HTML、CSS和JavaScript,再加上两个第三方库来构建这个应用程序:

  • PDF.js——用于在浏览器中渲染PDF页面。

  • PDF-lib——用于生成最终的反转PDF文件。

我们的项目结构如下所示:

pdf-color-inverter/

│── index.html
│── style.css
│── script.js
│── pdf.worker.min.js
│── assets/

将各个组件分开存放,可以使应用程序更易于理解和维护。

在加载自己的JavaScript代码之前,请先引入所需的第三方库。

<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.4.168/pdf.min.js"></script>

<script src="https://unpkg.com/pdf-lib/dist/pdf-lib.min.js"></script>

<script src="script.js"></script>

需要配置PDF.js的工作进程。

pdfjsLib.GlobalWorkerOptions.workerSrc =
    "pdf.worker.min.js";

在后台进程中运行PDF.js,可以在渲染大型PDF文件时保持浏览器的响应速度。

创建HTML布局

这个应用程序包含四个主要的组成部分:

  • 上传区域

  • 设置面板

  • PDF预览功能

  • 结果展示区

首先来创建这些基本的结构吧。

<section id="uploadSection"></section>

<section id="settingsSection" hidden></section>

<section id="previewSection" hidden></section>

<section id="resultSection" hidden></section>

页面首次加载时,只有上传区域是可见的。

在选择了一个PDF文件后,其余的部分才会变得可用。

选择主要元素

在应用程序中保存对各种元素的引用。

const uploadSection =
document.getElementById(
    "uploadSection"
);

const settingsSection =
document.getElementById(
    "settingsSection"
);

const previewSection =
document.getElementById(
    "previewSection"
);

const resultSection =
document.getElementById(
    "resultSection"
);

const pdfCanvas =
document.getElementById(
    "pdfCanvas"
);

这些引用使得在工作流程的不同阶段之间切换变得非常方便。

上传和预览PDF文件

上传区域支持拖放操作,也支持手动选择文件。

当选择了一个文件后,需要确认该文件确实是PDF格式。

async function handleUpload(
    file
) {

    if (
        !file ||
        file.type !==
        "application/pdf"
    ) {

        alert(
            "请选择一个PDF文件。"
        );

        return;

    }

    await loadPdf(file);

}

如果文件通过了验证,浏览器会将其加载到内存中。

支持受密码保护的PDF文件

有些文档是受到密码保护的。

在开始处理文件之前,上传界面会提供一个可选的密码输入框。

上传界面中提供了用于输入受密码保护PDF文件的密码字段。

系统会获取用户输入的密码。

const password =
document
.getElementById(
    "pdfPassword"
)
.value
.trim();

在加载PDF文件时,需要将这个密码传递给PDF.js库。

const loadingTask =
pdfjsLib.getDocument、

    data: pdfBytes,

    password

);

pdfDocument =
await loadingTask.promise;

如果文档没有设置密码保护,那么密码输入框就可以保持为空状态。

加载PDF文件

需要将上传的文件转换成ArrayBuffer格式。

async function loadPdf(
    file
) {

    originalPdfBytes =
    await file.arrayBuffer();

    pdfDocument =
    await pdfjsLib
        .getDocument、

            data:
            originalPdfBytes

        )

        .promise;

    currentPage = 1;

    await renderPage(
        currentPage
    );

}

原始字节会被保留下来,因为后续会使用这些字节来生成反转后的PDF文件。

渲染PDF页面

PDF.js是一次只渲染一个页面。

获取用户请求的页面内容。

async function renderPage(
    pageNumber
) {

    const page =
    await pdfDocument.getPage(
        pageNumber
    );

    const viewport =
    page.getViewport({

        scale: 1.5
    });

调整画布的大小。

pdfCanvas.width =
viewport.width;

pdfCanvas.height =
viewport.height;

渲染该页面。

await page.render、

    canvasContext:

    pdfCanvas.getContext(
        "2d"
    ),

    viewport

}).promise;

当渲染完成后,所选页面就会在预览区域中显示出来。

文档上传完成后,会以逐页的形式显示PDF预览效果。

在页面之间导航

大多数文档都包含多个页面,因此预览界面会提供“上一页”和“下一页”按钮。

创建当前页面的状态信息。

let currentPage = 1;

let pdfDocument = null;

跳转到上一页。

previousButton
.addEventListener(

"click",

async () => {

    if (
        currentPage > 1
    ) {

        currentPage--;

        await renderPage(
            currentPage
        );

    }

});

跳转到下一页。

nextButton
.addEventListener(

"click",

async () => {

    if (

        currentPage < pdfDocument.numPages

    ) {

        currentPage++;

        await renderPage(
            currentPage
        );

    }

});

更新页面显示索引。

pageNumber.textContent =
`第${currentPage}页,共${pdfDocument.numPages}页`;

这样用户就可以在选择哪些页面需要反转颜色之前,先浏览整个文档内容。

设置颜色反转选项

在处理PDF文件之前,用户应该能够自行控制颜色的反转方式。设置面板允许用户选择反转模式、指定需要处理的页面范围、选择输出质量、开启实时预览功能,或者重置所有设置并重新开始操作。

PDF颜色反转设置面板,包含反转模式、页面范围、输出质量、实时预览及重置选项。

选择反转模式

第一个选项允许用户指定文档颜色应如何被反转。

创建一个下拉列表。

<select id="invertMode">

    

读取所选择的模式。

const inversionMode = document.getElementById("invertMode").value;

所选的设置决定了在处理文档时应用哪种颜色变换方式。

选择页面范围

有时用户只需要反转部分页面,而不是整个文档。

创建两个输入字段用于指定页面范围。

<>input type="number" id="startPage" min="1"> 

获取用户选择的页面范围。

const startPage = Number(document.getElementById("startPage").value); const endPage = Number(document.getElementById("endPage").value);

在生成最终PDF时,只有这个范围内指定的页面才会被处理。

选择输出质量

该工具提供了多种质量等级,让用户能够根据需要平衡图像质量和文件大小。

创建一个质量选择器。

<>select id="outputQuality">

    
输出质量选择器,提供低、中、高三种选项。

读取所选择的输出质量设置。

const quality = document.getElementById("outputQuality").value;

所选的输出质量将决定生成的新PDF的图像质量。

启用实时预览功能

实时预览功能允许用户在不生成新PDF的情况下立即查看颜色反转后的效果。

创建一个用于切换此功能的控件。

<>input type="checkbox" id="livePreview">

读取该控件的当前状态。

const livePreview = document.getElementById("livePreview").checked;

每当用户更改设置时,都需要重新刷新预览页面。

livePreviewToggle.addEventListener("change", updatePreview);

当实时预览功能被启用后,每当用户调整反转设置时,预览画面会自动更新。

已启用实时预览功能,可查看PDF页面的缩略图。

重置设置

“重置”按钮会清除当前的配置,这样用户就可以在不重新加载页面的情况下开始操作。

创建这个按钮。

<button id="resetButton">

重置/清除

</button>

将设置恢复为默认值。

function resetSettings() {

    invertMode.value =
    "full";

    outputQuality.value =
    "high";

    livePreview.checked =
    false;

}

为这个按钮添加事件监听器。

resetButton
.addEventListener(

"click",

resetSettings

);

执行此操作后,设置面板会恢复到初始状态。

开始颜色反转功能

在确认设置无误后,用户就可以开始处理文档了。

创建这个操作按钮。

<button id="invertPdf">

反转PDF页面的颜色

<>/button>

启动颜色反转过程。

invertButton
.addEventListener(

"click",

async () => {

    await invertPdf();

});
位于页面预览下方的“反转PDF颜色”按钮。

反转PDF页面的颜色

现在我们来实现这个应用程序的核心功能:反转每页PDF的颜色。

操作流程非常简单。首先,PDF.js会将页面内容渲染到HTML画布上;接着,JavaScript会读取画布上的每个像素,将其红色、绿色和蓝色的数值进行反转,然后再将这些更新后的像素写回画布中;最后,使用PDF-lib将处理后的页面生成为新的PDF文件。

读取画布上的像素数据

一旦页面内容被渲染出来,就可以获取其对应的像素信息了。

const imageData =

context.getImageData(

    0,

    0,

    canvas.width,

    canvas.height

);

每个像素由四个数值组成:

  • 红色

  • 绿色

  • 蓝色

  • 透明度

这些像素数据被存储在一个数组中。

const pixels =
imageData.data;

我们将直接修改这个数组来实现颜色反转的效果。

反转每个像素的颜色

要反转一种颜色的数值,只需将其RGB值从255中减去即可。

需要遍历画布上的每一个像素才能完成这个操作。

for (

    let i = 0;

    while (i < pixels.length) {

        i += 4;
    }

    pixels[i] =
    255 - pixels[i];

    pixels[i + 1] =
    255 - pixels[i + 1];

    pixels[i + 2] =
    255 - pixels[i + 2];
}

阿尔法通道保持不变,因此透明元素仍然能够正确显示。

在修改了每个像素之后,需要将修改后的图像重新绘制到画布上。

context.putImageData(

    imageData,

    0,

    0

);

现在,页面预览会显示出颜色被反转后的效果。

更新实时预览效果

如果启用了实时预览功能,用户可以立即看到更改结果,而无需重新生成PDF文件。

请检查该功能是否处于激活状态。

if (

    livePreview.checked

) {

    await invertCurrentPage();

}

每当用户更改颜色反转模式、页面范围或质量设置时,都需要重新刷新预览效果。

async function updatePreview() {

    await renderPage(

        currentPage

    );

    await invertCurrentPage();

}
实时预览功能已启用,当前显示的是颜色被反转后的PDF页面。

处理选定的页面

每次不需要处理整个文档,只需反转用户所选中的那些页面即可。

遍历用户选择的页面范围进行操作。

for (

    let page = startPage;

    page <= endPage;

    page++;

) {

    await processPage(
        page
    );

}

每处理完一个页面后,都会将其暂时存储起来,直到最终生成PDF文件时才会将其合并。

生成最终的PDF文件

创建一个新的PDF文档。

const outputPdf =

await PDFLib
.PDFDocument
.create();

将处理后的图像数据转换为图片格式。

const imageBytes =

await canvasToBytes(
    pdfCanvas
);

将生成的图片嵌入到PDF文档中。

const image =

await outputPdf
.embedPng(
    imageBytes
);

创建一个新的页面,并在该页面上放置图片。

const page =

outputPdf.addPage([

    image.width,

    image.height

]);

在新的页面上绘制处理后的图像。

page.drawImage(

    image,

    {

        x: 0,

        y: 0,

        width: image.width,

        height: image.height

    }

);

对于每一个被选中的页面,都需要重复上述步骤。

保存最终的PDF文件

当所有页面都处理完成后,就需要将文档保存下来。

const pdfBytes =

await outputPdf.save();

将生成的PDF字节数据转换为可供下载的文件格式。

generatedPdfBlob =

new Blob(

    [pdfBytes],

    {

        type:
        "application/pdf"

    }

);

生成的PDF文件现已可以用于预览和下载了。

预览结果

在下载处理后的文档之前,让用户先查看最终输出内容是非常有用的。这样他们就可以确认所选页面的颜色是否已经被正确反转,以及文档的显示效果是否符合预期。

将生成的PDF文件加载到PDF.js中。

async function showPreview() {

    const bytes =
        await generatedPdfBlob
            .arrayBuffer();

    finalPdf =
        await pdfjsLib
            .getDocument({

                data: bytes

            })
            .promise;

    renderFinalPage(1);

}

渲染所选页面。

async function renderFinalPage(
    pageNumber
) {

    const page =
        await finalPdf.getPage(
            pageNumber
        );

    const viewport =
        page.getViewport({

            scale: 1.5

        });

    finalCanvas.width =
        viewport.width;

    finalCanvas.height =
        viewport.height;

    await page.render、

        canvasContext:
        finalCanvas
            .getContext("2d"),

        viewport

    }).promise;

}

用户可以在下载之前先浏览处理后的文档内容。

最终PDF预览图,显示在下载前的颜色反转效果。

重命名与下载

在保存PDF文件之前,用户可以为其指定自定义文件名。

创建用于输入文件名的字段。

<input

type="text"

id="outputFilename"

value="inverted-document.pdf">

获取用户输入的文件名。

function getFilename() {

    let filename =

    outputFilename
        .value
        .trim();

    if (!filename) {

        filename =
        "inverted-document.pdf";

    }

    if (
        !filename
        .endsWith(".pdf")
    ) {

        filename += ".pdf";

    }

    return filename;

}

显示额外的文件信息。

pageCount.textContent =

 `${finalPdf.numPages} 页`;

fileSize.textContent =

formatFileSize、

    generatedPdfBlob.size

);

下载生成的PDF文件。

downloadButton
.addEventListener(

"click",

() => {

    const url =

    URL.createObjectURL(
        generatedPdfBlob
    );

    const link =
        document.createElement(
            "a"
        );

    link.href =
        url;

    link.download =
        getFilename();

    link.click();

    URL.revokeObjectURL(
        url
    );

});

所有操作都在浏览器内部完成,因此用户可以在查看处理后的PDF文件后立即将其下载下来。

下载界面,显示重新命名的PDF文件名、页数、文件大小以及下载按钮。

演示:PDF颜色反转工具的工作原理

让我们一起来了解整个操作流程。

步骤1:上传PDF文件

用户只需将PDF文件拖放到上传区域,或点击“选择PDF文件”即可。如果文档设置了密码保护,可以在上传前输入密码。

支持拖放操作的上传界面,包含“选择PDF文件”按钮和密码输入框。

步骤2:预览文档

上传的PDF文件会逐页显示,用户可以在进行任何修改之前先浏览整个文档内容。

在应用颜色反转功能之前的PDF页面预览图。

步骤3:配置设置选项

用户可以选择需要处理的页面范围、输出文件的质量,以及是否启用实时预览功能。

设置面板,显示颜色反转选项、页面范围、输出质量及实时预览功能。

步骤4:预览反转后的颜色效果

如果启用了实时预览功能,当前页面会立即显示反转后的效果,用户可以提前查看修改后的文档外观。

步骤5:生成PDF文件

点击“反转PDF颜色”按钮后,系统会处理选中的页面,并生成新的PDF文件,其中包含所有经过颜色反转处理的页面。

步骤6:审核并下载文件

生成的PDF文件会在最终预览界面中显示。用户可以重新命名输出文件,查看页数和文件大小,然后下载完成的文档。

最终PDF预览界面,包含文件重命名选项、页数、文件大小及下载按钮。

性能优化技巧

较大的PDF文件可能需要更长的处理时间。仅渲染当前页面、更新预览内容而非重新加载整个文档,以及仅处理选中的页面范围,这些方法都能显著提升处理效率。

for (

    let page = startPage;

    page <= endPage;

    page++;

) {

    await processPage(
        page
    );

}

下载完成后,请释放临时使用的资源。

URL.revokeObjectURL(
    downloadUrl
);

这些小小的优化措施能够帮助应用程序在处理大型PDF文件时依然保持良好的响应速度。

常见错误

一个常见的错误是在没有先渲染PDF页面的新副本的情况下,反复修改原始画布内容。这种操作会导致颜色多次被反转。

在进行任何颜色调整之前,务必先渲染原始页面。

await renderPage(
    currentPage
);

另一个容易犯的错误是尝试处理超出有效范围的页码。

if (

    pageNumber < 1 ||

    pageNumber > pdfDocument.numPages

) {

    return;

}

最后需要记住的是,较高的输出质量通常会导致PDF文件体积增大。用户应根据自己的实际需求,选择能够兼顾图像清晰度与文件大小的设置。

总结

通过本教程,您使用JavaScript开发了一个基于浏览器的PDF颜色反转工具。

您学会了如何上传PDF文件、支持加密文件、利用PDF.js渲染页面、通过HTML Canvas API修改像素颜色、使用PDF-lib生成新的PDF文件、预览处理完成的文档、重命名输出文件,并直接从浏览器中下载它。

由于所有处理操作都是在本机进行的,因此用户可以在不将敏感文件上传到外部服务器的情况下完成PDF颜色的反转操作。

您可以通过这个PDF颜色反转工具来体验整个工作流程。

这个项目还可以进一步扩展:添加自定义颜色滤镜、选择性页面预览功能、亮度与对比度调节选项、灰度转换效果、棕褐色处理效果、批量处理功能,或是其他文档优化工具。

相关文章

技术实践

如何使用JavaScript开发一款基于浏览器的PDF模糊处理工具

许多PDF文件包含不应公开分享的信息。在将文件发送给他人之前,个人资料、财务数据、签名、地址、账号信息、员工资料或机密商业数据通常都需要被隐藏起来。 一款PDF模糊处理工具可以让这一过程变得简单。它并不会永久删除这些内容,而是会在PDF文件的指定区域添加模糊效果,这样一来,敏感信息就会变得难以阅读,而文件的其他部分则保持原样。 在这个教程中,你将使用JavaScript制作一个基于浏览器的PDF模糊处理工具。用户可以上传PDF文件,预览每一页,在敏感内容上标出需要模糊处理的区域,调整模糊程度,对选定的页面应用模糊效果,预览最终结果,然后下载处理后的PDF文件——整个过程都不需要将文件上传到服务

阅读全文
技术实践

如何在现代API中实施“以隐私保护为导向的设计理念”——开发人员的实用指南

作为软件开发人员,我们通常被教导要优先考虑速度、性能和正常运行时间等因素。在构建API时,我们的核心目标就是确保数据能够顺利地从A点传输到B点。 然而,全球范围内的数据隐私法规正在日益严格,用户也越来越关注自己的数字足迹。将隐私问题视为“事后才需要处理的法律事项”,或者认为可以在生产环境中再解决这些问题,已经不再是一种可持续的做法。 这时,《设计即隐私》这一理念就派上了用场。 “设计即隐私”是一个框架,旨在将隐私保护措施主动融入到工程开发的整个生命周期中。这意味着,你的系统架构应该默认就能保护用户数据。 在这份全面的指南中,我们将通过现代的工程模式、代码设计理念以及有针对性的数据库结构,来讲解

阅读全文
技术实践

从项目到产品:将平台转化为人们真正会使用的成品

仅仅拥有一个平台是远远不够的;真正的挑战在于确保该平台能够被用户理解、使用,并真正被他们采纳。只有当某种功能能够被他人可靠地使用时,它才算真正实现了其价值。为了评估开发工作的进展,你可以问自己:“这个功能真的被人们在使用吗?”“它是否减轻了用户的使用难度?”这样的思考有助于让开发工作始终与实际的用户需求保持一致,而不仅仅是追求功能的完成。 作者:本·林德斯

阅读全文
技术实践

如何使用shadcn/ui创建营销着陆页

大多数营销着陆页都会遇到同样的问题:你面对的只是一块空白屏幕,而你需要重新创建那些已经制作过无数次的页面元素。 标题栏、功能展示区、用户评价、价格信息、常见问题解答以及页脚这些内容都是常见的构建模块,但开发人员往往会在每个新项目中花费数小时来重新设计这些部分。 通过本指南,你将学习如何使用Next.js、shadcn/ui、Tailwind CSS以及可复用的shadcn组件来构建现代化的营销着陆页。你无需从零开始创建每一个页面元素,而是可以直接组装出一个可用于实际项目的完整页面,然后根据自己的品牌需求进行定制,从而获得一个适用于各种实际开发场景的基础框架。 目录 先决条件 我们要构建什么?

阅读全文