feat: add adjust colors and scanner effect pages with corresponding types and configurations
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -30,6 +30,9 @@ dist-ssr
|
|||||||
coverage/
|
coverage/
|
||||||
*.lcov
|
*.lcov
|
||||||
|
|
||||||
|
# Generated sitemap
|
||||||
|
public/sitemap.xml
|
||||||
|
|
||||||
#backup
|
#backup
|
||||||
.seo-backup
|
.seo-backup
|
||||||
libreoffice-wasm-package
|
libreoffice-wasm-package
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "Інвертаваць колеры",
|
"name": "Інвертаваць колеры",
|
||||||
"subtitle": "Стварыць версію PDF у \"цёмнай тэме\"."
|
"subtitle": "Стварыць версію PDF у \"цёмнай тэме\"."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Эфект сканера",
|
||||||
|
"subtitle": "Зрабіць PDF падобным на адсканіраваны дакумент.",
|
||||||
|
"scanSettings": "Налады сканіравання",
|
||||||
|
"colorspace": "Каляровая прастора",
|
||||||
|
"gray": "Градацыі шэрага",
|
||||||
|
"border": "Рамка",
|
||||||
|
"rotate": "Паварот",
|
||||||
|
"rotateVariance": "Варыяцыя павароту",
|
||||||
|
"brightness": "Яркасць",
|
||||||
|
"contrast": "Кантраст",
|
||||||
|
"blur": "Размыццё",
|
||||||
|
"noise": "Шум",
|
||||||
|
"yellowish": "Жаўтаватасць",
|
||||||
|
"resolution": "Раздзяляльнасць",
|
||||||
|
"processButton": "Ужыць эфект сканера"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Наладзіць колеры",
|
||||||
|
"subtitle": "Наладзьце яркасць, кантраст, насычанасць і іншае ў вашым PDF.",
|
||||||
|
"colorSettings": "Налады колеру",
|
||||||
|
"brightness": "Яркасць",
|
||||||
|
"contrast": "Кантраст",
|
||||||
|
"saturation": "Насычанасць",
|
||||||
|
"hueShift": "Зрух адцення",
|
||||||
|
"temperature": "Тэмпература",
|
||||||
|
"tint": "Адценне",
|
||||||
|
"gamma": "Гама",
|
||||||
|
"sepia": "Сэпія",
|
||||||
|
"processButton": "Прымяніць налады колеру"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Колер фону",
|
"name": "Колер фону",
|
||||||
"subtitle": "Змяніць колер фону PDF."
|
"subtitle": "Змяніць колер фону PDF."
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "Farben invertieren",
|
"name": "Farben invertieren",
|
||||||
"subtitle": "Eine \"Dunkelmodus\"-Version Ihrer PDF erstellen."
|
"subtitle": "Eine \"Dunkelmodus\"-Version Ihrer PDF erstellen."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Scanner-Effekt",
|
||||||
|
"subtitle": "Lassen Sie Ihr PDF wie ein gescanntes Dokument aussehen.",
|
||||||
|
"scanSettings": "Scan-Einstellungen",
|
||||||
|
"colorspace": "Farbraum",
|
||||||
|
"gray": "Grau",
|
||||||
|
"border": "Rand",
|
||||||
|
"rotate": "Drehen",
|
||||||
|
"rotateVariance": "Drehvarianz",
|
||||||
|
"brightness": "Helligkeit",
|
||||||
|
"contrast": "Kontrast",
|
||||||
|
"blur": "Unschärfe",
|
||||||
|
"noise": "Rauschen",
|
||||||
|
"yellowish": "Gelbstich",
|
||||||
|
"resolution": "Auflösung",
|
||||||
|
"processButton": "Scanner-Effekt anwenden"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Farben anpassen",
|
||||||
|
"subtitle": "Helligkeit, Kontrast, Sättigung und mehr in Ihrem PDF feinabstimmen.",
|
||||||
|
"colorSettings": "Farbeinstellungen",
|
||||||
|
"brightness": "Helligkeit",
|
||||||
|
"contrast": "Kontrast",
|
||||||
|
"saturation": "Sättigung",
|
||||||
|
"hueShift": "Farbton",
|
||||||
|
"temperature": "Temperatur",
|
||||||
|
"tint": "Tönung",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Sepia",
|
||||||
|
"processButton": "Farbanpassungen anwenden"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Hintergrundfarbe",
|
"name": "Hintergrundfarbe",
|
||||||
"subtitle": "Die Hintergrundfarbe Ihrer PDF ändern."
|
"subtitle": "Die Hintergrundfarbe Ihrer PDF ändern."
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "Invert Colors",
|
"name": "Invert Colors",
|
||||||
"subtitle": "Create a \"dark mode\" version of your PDF."
|
"subtitle": "Create a \"dark mode\" version of your PDF."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Scanner Effect",
|
||||||
|
"subtitle": "Make your PDF look like a scanned document.",
|
||||||
|
"scanSettings": "Scan Settings",
|
||||||
|
"colorspace": "Colorspace",
|
||||||
|
"gray": "Gray",
|
||||||
|
"border": "Border",
|
||||||
|
"rotate": "Rotate",
|
||||||
|
"rotateVariance": "Rotate Variance",
|
||||||
|
"brightness": "Brightness",
|
||||||
|
"contrast": "Contrast",
|
||||||
|
"blur": "Blur",
|
||||||
|
"noise": "Noise",
|
||||||
|
"yellowish": "Yellowish",
|
||||||
|
"resolution": "Resolution",
|
||||||
|
"processButton": "Apply Scanner Effect"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Adjust Colors",
|
||||||
|
"subtitle": "Fine-tune brightness, contrast, saturation and more in your PDF.",
|
||||||
|
"colorSettings": "Color Settings",
|
||||||
|
"brightness": "Brightness",
|
||||||
|
"contrast": "Contrast",
|
||||||
|
"saturation": "Saturation",
|
||||||
|
"hueShift": "Hue Shift",
|
||||||
|
"temperature": "Temperature",
|
||||||
|
"tint": "Tint",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Sepia",
|
||||||
|
"processButton": "Apply Color Adjustments"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Background Color",
|
"name": "Background Color",
|
||||||
"subtitle": "Change the background color of your PDF."
|
"subtitle": "Change the background color of your PDF."
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "Invertir Colores",
|
"name": "Invertir Colores",
|
||||||
"subtitle": "Crea una versión en \"modo oscuro\" de tu PDF."
|
"subtitle": "Crea una versión en \"modo oscuro\" de tu PDF."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Efecto escáner",
|
||||||
|
"subtitle": "Haz que tu PDF parezca un documento escaneado.",
|
||||||
|
"scanSettings": "Ajustes de escaneo",
|
||||||
|
"colorspace": "Espacio de color",
|
||||||
|
"gray": "Gris",
|
||||||
|
"border": "Borde",
|
||||||
|
"rotate": "Rotar",
|
||||||
|
"rotateVariance": "Variación de rotación",
|
||||||
|
"brightness": "Brillo",
|
||||||
|
"contrast": "Contraste",
|
||||||
|
"blur": "Desenfoque",
|
||||||
|
"noise": "Ruido",
|
||||||
|
"yellowish": "Amarillento",
|
||||||
|
"resolution": "Resolución",
|
||||||
|
"processButton": "Aplicar efecto escáner"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Ajustar colores",
|
||||||
|
"subtitle": "Ajusta brillo, contraste, saturación y más en tu PDF.",
|
||||||
|
"colorSettings": "Configuración de color",
|
||||||
|
"brightness": "Brillo",
|
||||||
|
"contrast": "Contraste",
|
||||||
|
"saturation": "Saturación",
|
||||||
|
"hueShift": "Tono",
|
||||||
|
"temperature": "Temperatura",
|
||||||
|
"tint": "Matiz",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Sepia",
|
||||||
|
"processButton": "Aplicar ajustes de color"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Color de Fondo",
|
"name": "Color de Fondo",
|
||||||
"subtitle": "Cambia el color de fondo de tu PDF."
|
"subtitle": "Cambia el color de fondo de tu PDF."
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "Inverser les couleurs",
|
"name": "Inverser les couleurs",
|
||||||
"subtitle": "Créer une version « mode sombre » du PDF."
|
"subtitle": "Créer une version « mode sombre » du PDF."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Effet scanner",
|
||||||
|
"subtitle": "Donnez à votre PDF l'apparence d'un document scanné.",
|
||||||
|
"scanSettings": "Paramètres de numérisation",
|
||||||
|
"colorspace": "Espace colorimétrique",
|
||||||
|
"gray": "Gris",
|
||||||
|
"border": "Bordure",
|
||||||
|
"rotate": "Rotation",
|
||||||
|
"rotateVariance": "Variance de rotation",
|
||||||
|
"brightness": "Luminosité",
|
||||||
|
"contrast": "Contraste",
|
||||||
|
"blur": "Flou",
|
||||||
|
"noise": "Bruit",
|
||||||
|
"yellowish": "Jaunissement",
|
||||||
|
"resolution": "Résolution",
|
||||||
|
"processButton": "Appliquer l'effet scanner"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Ajuster les couleurs",
|
||||||
|
"subtitle": "Affinez la luminosité, le contraste, la saturation et plus dans votre PDF.",
|
||||||
|
"colorSettings": "Paramètres de couleur",
|
||||||
|
"brightness": "Luminosité",
|
||||||
|
"contrast": "Contraste",
|
||||||
|
"saturation": "Saturation",
|
||||||
|
"hueShift": "Teinte",
|
||||||
|
"temperature": "Température",
|
||||||
|
"tint": "Nuance",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Sépia",
|
||||||
|
"processButton": "Appliquer les ajustements"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Couleur de fond",
|
"name": "Couleur de fond",
|
||||||
"subtitle": "Modifier la couleur de fond du PDF."
|
"subtitle": "Modifier la couleur de fond du PDF."
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "Balik Warna",
|
"name": "Balik Warna",
|
||||||
"subtitle": "Buat versi \"mode gelap\" dari PDF Anda."
|
"subtitle": "Buat versi \"mode gelap\" dari PDF Anda."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Efek Pemindai",
|
||||||
|
"subtitle": "Buat PDF Anda terlihat seperti dokumen yang dipindai.",
|
||||||
|
"scanSettings": "Pengaturan Pemindaian",
|
||||||
|
"colorspace": "Ruang Warna",
|
||||||
|
"gray": "Skala Abu-abu",
|
||||||
|
"border": "Batas",
|
||||||
|
"rotate": "Putar",
|
||||||
|
"rotateVariance": "Variasi Putaran",
|
||||||
|
"brightness": "Kecerahan",
|
||||||
|
"contrast": "Kontras",
|
||||||
|
"blur": "Blur",
|
||||||
|
"noise": "Noise",
|
||||||
|
"yellowish": "Kekuningan",
|
||||||
|
"resolution": "Resolusi",
|
||||||
|
"processButton": "Terapkan Efek Pemindai"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Sesuaikan Warna",
|
||||||
|
"subtitle": "Sesuaikan kecerahan, kontras, saturasi dan lainnya pada PDF Anda.",
|
||||||
|
"colorSettings": "Pengaturan Warna",
|
||||||
|
"brightness": "Kecerahan",
|
||||||
|
"contrast": "Kontras",
|
||||||
|
"saturation": "Saturasi",
|
||||||
|
"hueShift": "Pergeseran Rona",
|
||||||
|
"temperature": "Suhu",
|
||||||
|
"tint": "Pewarnaan",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Sepia",
|
||||||
|
"processButton": "Terapkan Penyesuaian Warna"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Warna Latar Belakang",
|
"name": "Warna Latar Belakang",
|
||||||
"subtitle": "Ubah warna latar belakang PDF Anda."
|
"subtitle": "Ubah warna latar belakang PDF Anda."
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "Inverti Colori",
|
"name": "Inverti Colori",
|
||||||
"subtitle": "Crea una versione \"modalità scura\" del tuo PDF."
|
"subtitle": "Crea una versione \"modalità scura\" del tuo PDF."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Effetto scanner",
|
||||||
|
"subtitle": "Fai sembrare il tuo PDF un documento scansionato.",
|
||||||
|
"scanSettings": "Impostazioni di scansione",
|
||||||
|
"colorspace": "Spazio colore",
|
||||||
|
"gray": "Grigio",
|
||||||
|
"border": "Bordo",
|
||||||
|
"rotate": "Rotazione",
|
||||||
|
"rotateVariance": "Variazione rotazione",
|
||||||
|
"brightness": "Luminosità",
|
||||||
|
"contrast": "Contrasto",
|
||||||
|
"blur": "Sfocatura",
|
||||||
|
"noise": "Rumore",
|
||||||
|
"yellowish": "Ingiallimento",
|
||||||
|
"resolution": "Risoluzione",
|
||||||
|
"processButton": "Applica effetto scanner"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Regola colori",
|
||||||
|
"subtitle": "Regola luminosità, contrasto, saturazione e altro nel tuo PDF.",
|
||||||
|
"colorSettings": "Impostazioni colore",
|
||||||
|
"brightness": "Luminosità",
|
||||||
|
"contrast": "Contrasto",
|
||||||
|
"saturation": "Saturazione",
|
||||||
|
"hueShift": "Tonalità",
|
||||||
|
"temperature": "Temperatura",
|
||||||
|
"tint": "Tinta",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Seppia",
|
||||||
|
"processButton": "Applica regolazioni colore"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Colore di Sfondo",
|
"name": "Colore di Sfondo",
|
||||||
"subtitle": "Cambia il colore di sfondo del tuo PDF."
|
"subtitle": "Cambia il colore di sfondo del tuo PDF."
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "Kleuren Omkeren",
|
"name": "Kleuren Omkeren",
|
||||||
"subtitle": "Maak een \"donkere modus\"-versie van je PDF."
|
"subtitle": "Maak een \"donkere modus\"-versie van je PDF."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Scannereffect",
|
||||||
|
"subtitle": "Laat je PDF eruitzien als een gescand document.",
|
||||||
|
"scanSettings": "Scaninstellingen",
|
||||||
|
"colorspace": "Kleurruimte",
|
||||||
|
"gray": "Grijswaarden",
|
||||||
|
"border": "Rand",
|
||||||
|
"rotate": "Roteren",
|
||||||
|
"rotateVariance": "Rotatievariatie",
|
||||||
|
"brightness": "Helderheid",
|
||||||
|
"contrast": "Contrast",
|
||||||
|
"blur": "Vervaging",
|
||||||
|
"noise": "Ruis",
|
||||||
|
"yellowish": "Geelheid",
|
||||||
|
"resolution": "Resolutie",
|
||||||
|
"processButton": "Scannereffect toepassen"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Kleuren aanpassen",
|
||||||
|
"subtitle": "Pas helderheid, contrast, verzadiging en meer aan in uw PDF.",
|
||||||
|
"colorSettings": "Kleurinstellingen",
|
||||||
|
"brightness": "Helderheid",
|
||||||
|
"contrast": "Contrast",
|
||||||
|
"saturation": "Verzadiging",
|
||||||
|
"hueShift": "Tintrotatie",
|
||||||
|
"temperature": "Temperatuur",
|
||||||
|
"tint": "Tint",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Sepia",
|
||||||
|
"processButton": "Kleuraanpassingen toepassen"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Achtergrondkleur",
|
"name": "Achtergrondkleur",
|
||||||
"subtitle": "Wijzig de achtergrondkleur van je PDF."
|
"subtitle": "Wijzig de achtergrondkleur van je PDF."
|
||||||
|
|||||||
@@ -76,6 +76,37 @@
|
|||||||
"name": "Inverter Cores",
|
"name": "Inverter Cores",
|
||||||
"subtitle": "Crie uma versão em \"modo escuro\" do seu PDF."
|
"subtitle": "Crie uma versão em \"modo escuro\" do seu PDF."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Efeito Scanner",
|
||||||
|
"subtitle": "Faça seu PDF parecer um documento digitalizado.",
|
||||||
|
"scanSettings": "Configurações de Digitalização",
|
||||||
|
"colorspace": "Espaço de Cor",
|
||||||
|
"gray": "Cinza",
|
||||||
|
"border": "Borda",
|
||||||
|
"rotate": "Rotação",
|
||||||
|
"rotateVariance": "Variação de Rotação",
|
||||||
|
"brightness": "Brilho",
|
||||||
|
"contrast": "Contraste",
|
||||||
|
"blur": "Desfoque",
|
||||||
|
"noise": "Ruído",
|
||||||
|
"yellowish": "Amarelado",
|
||||||
|
"resolution": "Resolução",
|
||||||
|
"processButton": "Aplicar Efeito Scanner"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Ajustar Cores",
|
||||||
|
"subtitle": "Ajuste brilho, contraste, saturação e mais no seu PDF.",
|
||||||
|
"colorSettings": "Configurações de Cor",
|
||||||
|
"brightness": "Brilho",
|
||||||
|
"contrast": "Contraste",
|
||||||
|
"saturation": "Saturação",
|
||||||
|
"hueShift": "Matiz",
|
||||||
|
"temperature": "Temperatura",
|
||||||
|
"tint": "Tonalidade",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Sépia",
|
||||||
|
"processButton": "Aplicar Ajustes de Cor"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Cor de Fundo",
|
"name": "Cor de Fundo",
|
||||||
"subtitle": "Altere a cor de fundo do seu PDF."
|
"subtitle": "Altere a cor de fundo do seu PDF."
|
||||||
|
|||||||
@@ -76,6 +76,37 @@
|
|||||||
"name": "Renkleri Ters Çevir",
|
"name": "Renkleri Ters Çevir",
|
||||||
"subtitle": "PDF'niz için \"karanlık mod\" sürümü oluşturun."
|
"subtitle": "PDF'niz için \"karanlık mod\" sürümü oluşturun."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Tarayıcı Efekti",
|
||||||
|
"subtitle": "PDF'nizi taranmış bir belge gibi gösterin.",
|
||||||
|
"scanSettings": "Tarama Ayarları",
|
||||||
|
"colorspace": "Renk Alanı",
|
||||||
|
"gray": "Gri",
|
||||||
|
"border": "Kenarlık",
|
||||||
|
"rotate": "Döndür",
|
||||||
|
"rotateVariance": "Döndürme Varyansı",
|
||||||
|
"brightness": "Parlaklık",
|
||||||
|
"contrast": "Kontrast",
|
||||||
|
"blur": "Bulanıklık",
|
||||||
|
"noise": "Gürültü",
|
||||||
|
"yellowish": "Sarımsı",
|
||||||
|
"resolution": "Çözünürlük",
|
||||||
|
"processButton": "Tarayıcı Efekti Uygula"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Renkleri Ayarla",
|
||||||
|
"subtitle": "PDF'inizde parlaklık, kontrast, doygunluk ve daha fazlasını ayarlayın.",
|
||||||
|
"colorSettings": "Renk Ayarları",
|
||||||
|
"brightness": "Parlaklık",
|
||||||
|
"contrast": "Kontrast",
|
||||||
|
"saturation": "Doygunluk",
|
||||||
|
"hueShift": "Ton Kaydırma",
|
||||||
|
"temperature": "Sıcaklık",
|
||||||
|
"tint": "Renk Tonu",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Sepya",
|
||||||
|
"processButton": "Renk Ayarlarını Uygula"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Arka Plan Rengi",
|
"name": "Arka Plan Rengi",
|
||||||
"subtitle": "PDF'nizin arka plan rengini değiştirin."
|
"subtitle": "PDF'nizin arka plan rengini değiştirin."
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "Đảo ngược màu",
|
"name": "Đảo ngược màu",
|
||||||
"subtitle": "Tạo phiên bản \"chế độ tối\" cho PDF của bạn."
|
"subtitle": "Tạo phiên bản \"chế độ tối\" cho PDF của bạn."
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "Hiệu ứng máy quét",
|
||||||
|
"subtitle": "Làm cho PDF trông giống như tài liệu đã quét.",
|
||||||
|
"scanSettings": "Cài đặt quét",
|
||||||
|
"colorspace": "Không gian màu",
|
||||||
|
"gray": "Thang xám",
|
||||||
|
"border": "Viền",
|
||||||
|
"rotate": "Xoay",
|
||||||
|
"rotateVariance": "Biên độ xoay",
|
||||||
|
"brightness": "Độ sáng",
|
||||||
|
"contrast": "Độ tương phản",
|
||||||
|
"blur": "Làm mờ",
|
||||||
|
"noise": "Nhiễu",
|
||||||
|
"yellowish": "Ngả vàng",
|
||||||
|
"resolution": "Độ phân giải",
|
||||||
|
"processButton": "Áp dụng hiệu ứng máy quét"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "Điều chỉnh màu sắc",
|
||||||
|
"subtitle": "Tinh chỉnh độ sáng, tương phản, độ bão hòa và nhiều hơn nữa.",
|
||||||
|
"colorSettings": "Cài đặt màu sắc",
|
||||||
|
"brightness": "Độ sáng",
|
||||||
|
"contrast": "Tương phản",
|
||||||
|
"saturation": "Độ bão hòa",
|
||||||
|
"hueShift": "Dịch chuyển sắc độ",
|
||||||
|
"temperature": "Nhiệt độ màu",
|
||||||
|
"tint": "Sắc thái",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "Sepia",
|
||||||
|
"processButton": "Áp dụng điều chỉnh màu"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "Màu nền",
|
"name": "Màu nền",
|
||||||
"subtitle": "Thay đổi màu nền của PDF của bạn."
|
"subtitle": "Thay đổi màu nền của PDF của bạn."
|
||||||
|
|||||||
@@ -76,6 +76,37 @@
|
|||||||
"name": "反轉顏色",
|
"name": "反轉顏色",
|
||||||
"subtitle": "為你的 PDF 建立深色版本。"
|
"subtitle": "為你的 PDF 建立深色版本。"
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "掃描效果",
|
||||||
|
"subtitle": "讓你的 PDF 看起來像掃描文件。",
|
||||||
|
"scanSettings": "掃描設定",
|
||||||
|
"colorspace": "色彩空間",
|
||||||
|
"gray": "灰階",
|
||||||
|
"border": "邊框",
|
||||||
|
"rotate": "旋轉",
|
||||||
|
"rotateVariance": "旋轉變異",
|
||||||
|
"brightness": "亮度",
|
||||||
|
"contrast": "對比度",
|
||||||
|
"blur": "模糊",
|
||||||
|
"noise": "雜訊",
|
||||||
|
"yellowish": "泛黃",
|
||||||
|
"resolution": "解析度",
|
||||||
|
"processButton": "套用掃描效果"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "調整顏色",
|
||||||
|
"subtitle": "微調 PDF 的亮度、對比度、飽和度等。",
|
||||||
|
"colorSettings": "顏色設定",
|
||||||
|
"brightness": "亮度",
|
||||||
|
"contrast": "對比度",
|
||||||
|
"saturation": "飽和度",
|
||||||
|
"hueShift": "色相偏移",
|
||||||
|
"temperature": "色溫",
|
||||||
|
"tint": "色調",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "復古色",
|
||||||
|
"processButton": "套用顏色調整"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "背景顏色",
|
"name": "背景顏色",
|
||||||
"subtitle": "更改你的 PDF 的背景顏色。"
|
"subtitle": "更改你的 PDF 的背景顏色。"
|
||||||
|
|||||||
@@ -98,6 +98,37 @@
|
|||||||
"name": "反转颜色",
|
"name": "反转颜色",
|
||||||
"subtitle": "创建您的 PDF 的“暗黑模式”版本。"
|
"subtitle": "创建您的 PDF 的“暗黑模式”版本。"
|
||||||
},
|
},
|
||||||
|
"scannerEffect": {
|
||||||
|
"name": "扫描效果",
|
||||||
|
"subtitle": "让您的 PDF 看起来像扫描文件。",
|
||||||
|
"scanSettings": "扫描设置",
|
||||||
|
"colorspace": "色彩空间",
|
||||||
|
"gray": "灰度",
|
||||||
|
"border": "边框",
|
||||||
|
"rotate": "旋转",
|
||||||
|
"rotateVariance": "旋转偏差",
|
||||||
|
"brightness": "亮度",
|
||||||
|
"contrast": "对比度",
|
||||||
|
"blur": "模糊",
|
||||||
|
"noise": "噪点",
|
||||||
|
"yellowish": "泛黄",
|
||||||
|
"resolution": "分辨率",
|
||||||
|
"processButton": "应用扫描效果"
|
||||||
|
},
|
||||||
|
"adjustColors": {
|
||||||
|
"name": "调整颜色",
|
||||||
|
"subtitle": "微调 PDF 的亮度、对比度、饱和度等。",
|
||||||
|
"colorSettings": "颜色设置",
|
||||||
|
"brightness": "亮度",
|
||||||
|
"contrast": "对比度",
|
||||||
|
"saturation": "饱和度",
|
||||||
|
"hueShift": "色相偏移",
|
||||||
|
"temperature": "色温",
|
||||||
|
"tint": "色调",
|
||||||
|
"gamma": "Gamma",
|
||||||
|
"sepia": "复古色",
|
||||||
|
"processButton": "应用颜色调整"
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"name": "背景颜色",
|
"name": "背景颜色",
|
||||||
"subtitle": "更改您的 PDF 的背景颜色。"
|
"subtitle": "更改您的 PDF 的背景颜色。"
|
||||||
|
|||||||
23962
public/sitemap.xml
23962
public/sitemap.xml
File diff suppressed because it is too large
Load Diff
@@ -119,6 +119,18 @@ export const categories = [
|
|||||||
icon: 'ph-circle-half',
|
icon: 'ph-circle-half',
|
||||||
subtitle: 'Create a "dark mode" version of your PDF.',
|
subtitle: 'Create a "dark mode" version of your PDF.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
href: import.meta.env.BASE_URL + 'scanner-effect.html',
|
||||||
|
name: 'Scanner Effect',
|
||||||
|
icon: 'ph-scan',
|
||||||
|
subtitle: 'Make your PDF look like a scanned document.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: import.meta.env.BASE_URL + 'adjust-colors.html',
|
||||||
|
name: 'Adjust Colors',
|
||||||
|
icon: 'ph-sliders-horizontal',
|
||||||
|
subtitle: 'Fine-tune brightness, contrast, saturation and more.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: import.meta.env.BASE_URL + 'background-color.html',
|
href: import.meta.env.BASE_URL + 'background-color.html',
|
||||||
name: 'Background Color',
|
name: 'Background Color',
|
||||||
|
|||||||
553
src/js/logic/adjust-colors-page.ts
Normal file
553
src/js/logic/adjust-colors-page.ts
Normal file
@@ -0,0 +1,553 @@
|
|||||||
|
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||||
|
import {
|
||||||
|
downloadFile,
|
||||||
|
formatBytes,
|
||||||
|
readFileAsArrayBuffer,
|
||||||
|
getPDFDocument,
|
||||||
|
} from '../utils/helpers.js';
|
||||||
|
import { createIcons, icons } from 'lucide';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
|
import type { AdjustColorsSettings } from '../types/adjust-colors-type.js';
|
||||||
|
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
|
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||||
|
import.meta.url
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
let files: File[] = [];
|
||||||
|
let cachedBaselineData: ImageData | null = null;
|
||||||
|
let cachedBaselineWidth = 0;
|
||||||
|
let cachedBaselineHeight = 0;
|
||||||
|
let pdfjsDoc: pdfjsLib.PDFDocumentProxy | null = null;
|
||||||
|
|
||||||
|
function getSettings(): AdjustColorsSettings {
|
||||||
|
return {
|
||||||
|
brightness: parseInt(
|
||||||
|
(document.getElementById('setting-brightness') as HTMLInputElement)
|
||||||
|
?.value ?? '0'
|
||||||
|
),
|
||||||
|
contrast: parseInt(
|
||||||
|
(document.getElementById('setting-contrast') as HTMLInputElement)
|
||||||
|
?.value ?? '0'
|
||||||
|
),
|
||||||
|
saturation: parseInt(
|
||||||
|
(document.getElementById('setting-saturation') as HTMLInputElement)
|
||||||
|
?.value ?? '0'
|
||||||
|
),
|
||||||
|
hueShift: parseInt(
|
||||||
|
(document.getElementById('setting-hue-shift') as HTMLInputElement)
|
||||||
|
?.value ?? '0'
|
||||||
|
),
|
||||||
|
temperature: parseInt(
|
||||||
|
(document.getElementById('setting-temperature') as HTMLInputElement)
|
||||||
|
?.value ?? '0'
|
||||||
|
),
|
||||||
|
tint: parseInt(
|
||||||
|
(document.getElementById('setting-tint') as HTMLInputElement)?.value ??
|
||||||
|
'0'
|
||||||
|
),
|
||||||
|
gamma: parseFloat(
|
||||||
|
(document.getElementById('setting-gamma') as HTMLInputElement)?.value ??
|
||||||
|
'1.0'
|
||||||
|
),
|
||||||
|
sepia: parseInt(
|
||||||
|
(document.getElementById('setting-sepia') as HTMLInputElement)?.value ??
|
||||||
|
'0'
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
|
||||||
|
r /= 255;
|
||||||
|
g /= 255;
|
||||||
|
b /= 255;
|
||||||
|
const max = Math.max(r, g, b);
|
||||||
|
const min = Math.min(r, g, b);
|
||||||
|
const l = (max + min) / 2;
|
||||||
|
let h = 0;
|
||||||
|
let s = 0;
|
||||||
|
|
||||||
|
if (max !== min) {
|
||||||
|
const d = max - min;
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
||||||
|
else if (max === g) h = ((b - r) / d + 2) / 6;
|
||||||
|
else h = ((r - g) / d + 4) / 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [h, s, l];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hslToRgb(h: number, s: number, l: number): [number, number, number] {
|
||||||
|
if (s === 0) {
|
||||||
|
const v = Math.round(l * 255);
|
||||||
|
return [v, v, v];
|
||||||
|
}
|
||||||
|
|
||||||
|
const hue2rgb = (p: number, q: number, t: number): number => {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1 / 2) return q;
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
const p = 2 * l - q;
|
||||||
|
return [
|
||||||
|
Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
|
||||||
|
Math.round(hue2rgb(p, q, h) * 255),
|
||||||
|
Math.round(hue2rgb(p, q, h - 1 / 3) * 255),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyEffects(
|
||||||
|
sourceData: ImageData,
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
settings: AdjustColorsSettings
|
||||||
|
): void {
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
const w = sourceData.width;
|
||||||
|
const h = sourceData.height;
|
||||||
|
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
|
||||||
|
const imageData = new ImageData(new Uint8ClampedArray(sourceData.data), w, h);
|
||||||
|
const data = imageData.data;
|
||||||
|
|
||||||
|
const contrastFactor =
|
||||||
|
settings.contrast !== 0
|
||||||
|
? (259 * (settings.contrast + 255)) / (255 * (259 - settings.contrast))
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
const gammaCorrection = settings.gamma !== 1.0 ? 1 / settings.gamma : 1;
|
||||||
|
const sepiaAmount = settings.sepia / 100;
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
|
let r = data[i];
|
||||||
|
let g = data[i + 1];
|
||||||
|
let b = data[i + 2];
|
||||||
|
|
||||||
|
// Brightness
|
||||||
|
if (settings.brightness !== 0) {
|
||||||
|
const adj = settings.brightness * 2.55;
|
||||||
|
r += adj;
|
||||||
|
g += adj;
|
||||||
|
b += adj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contrast
|
||||||
|
if (settings.contrast !== 0) {
|
||||||
|
r = contrastFactor * (r - 128) + 128;
|
||||||
|
g = contrastFactor * (g - 128) + 128;
|
||||||
|
b = contrastFactor * (b - 128) + 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saturation and Hue Shift (via HSL)
|
||||||
|
if (settings.saturation !== 0 || settings.hueShift !== 0) {
|
||||||
|
const [hue, sat, lig] = rgbToHsl(
|
||||||
|
Math.max(0, Math.min(255, r)),
|
||||||
|
Math.max(0, Math.min(255, g)),
|
||||||
|
Math.max(0, Math.min(255, b))
|
||||||
|
);
|
||||||
|
|
||||||
|
let newHue = hue;
|
||||||
|
if (settings.hueShift !== 0) {
|
||||||
|
newHue = (hue + settings.hueShift / 360) % 1;
|
||||||
|
if (newHue < 0) newHue += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newSat = sat;
|
||||||
|
if (settings.saturation !== 0) {
|
||||||
|
const satAdj = settings.saturation / 100;
|
||||||
|
newSat = satAdj > 0 ? sat + (1 - sat) * satAdj : sat * (1 + satAdj);
|
||||||
|
newSat = Math.max(0, Math.min(1, newSat));
|
||||||
|
}
|
||||||
|
|
||||||
|
[r, g, b] = hslToRgb(newHue, newSat, lig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temperature (warm/cool shift)
|
||||||
|
if (settings.temperature !== 0) {
|
||||||
|
const t = settings.temperature / 50;
|
||||||
|
r += 30 * t;
|
||||||
|
b -= 30 * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tint (green-magenta axis)
|
||||||
|
if (settings.tint !== 0) {
|
||||||
|
const t = settings.tint / 50;
|
||||||
|
g += 30 * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gamma
|
||||||
|
if (settings.gamma !== 1.0) {
|
||||||
|
r = Math.pow(Math.max(0, Math.min(255, r)) / 255, gammaCorrection) * 255;
|
||||||
|
g = Math.pow(Math.max(0, Math.min(255, g)) / 255, gammaCorrection) * 255;
|
||||||
|
b = Math.pow(Math.max(0, Math.min(255, b)) / 255, gammaCorrection) * 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sepia
|
||||||
|
if (settings.sepia > 0) {
|
||||||
|
const sr = 0.393 * r + 0.769 * g + 0.189 * b;
|
||||||
|
const sg = 0.349 * r + 0.686 * g + 0.168 * b;
|
||||||
|
const sb = 0.272 * r + 0.534 * g + 0.131 * b;
|
||||||
|
r = r + (sr - r) * sepiaAmount;
|
||||||
|
g = g + (sg - g) * sepiaAmount;
|
||||||
|
b = b + (sb - b) * sepiaAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[i] = Math.max(0, Math.min(255, r));
|
||||||
|
data[i + 1] = Math.max(0, Math.min(255, g));
|
||||||
|
data[i + 2] = Math.max(0, Math.min(255, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePreview(): void {
|
||||||
|
if (!cachedBaselineData) return;
|
||||||
|
|
||||||
|
const previewCanvas = document.getElementById(
|
||||||
|
'preview-canvas'
|
||||||
|
) as HTMLCanvasElement;
|
||||||
|
if (!previewCanvas) return;
|
||||||
|
|
||||||
|
const settings = getSettings();
|
||||||
|
const baselineCopy = new ImageData(
|
||||||
|
new Uint8ClampedArray(cachedBaselineData.data),
|
||||||
|
cachedBaselineWidth,
|
||||||
|
cachedBaselineHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
applyEffects(baselineCopy, previewCanvas, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderPreview(): Promise<void> {
|
||||||
|
if (!pdfjsDoc) return;
|
||||||
|
|
||||||
|
const page = await pdfjsDoc.getPage(1);
|
||||||
|
const viewport = page.getViewport({ scale: 1.0 });
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
|
||||||
|
await page.render({ canvasContext: ctx, viewport, canvas }).promise;
|
||||||
|
|
||||||
|
cachedBaselineData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
cachedBaselineWidth = canvas.width;
|
||||||
|
cachedBaselineHeight = canvas.height;
|
||||||
|
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUI = () => {
|
||||||
|
const fileDisplayArea = document.getElementById('file-display-area');
|
||||||
|
const optionsPanel = document.getElementById('options-panel');
|
||||||
|
|
||||||
|
if (!fileDisplayArea || !optionsPanel) return;
|
||||||
|
|
||||||
|
fileDisplayArea.innerHTML = '';
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
optionsPanel.classList.remove('hidden');
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
const fileDiv = document.createElement('div');
|
||||||
|
fileDiv.className =
|
||||||
|
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||||
|
|
||||||
|
const infoContainer = document.createElement('div');
|
||||||
|
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||||
|
|
||||||
|
const nameSpan = document.createElement('div');
|
||||||
|
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||||
|
nameSpan.textContent = file.name;
|
||||||
|
|
||||||
|
const metaSpan = document.createElement('div');
|
||||||
|
metaSpan.className = 'text-xs text-gray-400';
|
||||||
|
metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`;
|
||||||
|
|
||||||
|
infoContainer.append(nameSpan, metaSpan);
|
||||||
|
|
||||||
|
const removeBtn = document.createElement('button');
|
||||||
|
removeBtn.className =
|
||||||
|
'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||||
|
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||||
|
removeBtn.onclick = () => {
|
||||||
|
files = [];
|
||||||
|
pdfjsDoc = null;
|
||||||
|
cachedBaselineData = null;
|
||||||
|
updateUI();
|
||||||
|
};
|
||||||
|
|
||||||
|
fileDiv.append(infoContainer, removeBtn);
|
||||||
|
fileDisplayArea.appendChild(fileDiv);
|
||||||
|
|
||||||
|
readFileAsArrayBuffer(file)
|
||||||
|
.then((buffer: ArrayBuffer) => {
|
||||||
|
return getPDFDocument(buffer).promise;
|
||||||
|
})
|
||||||
|
.then((pdf: pdfjsLib.PDFDocumentProxy) => {
|
||||||
|
metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
metaSpan.textContent = formatBytes(file.size);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
createIcons({ icons });
|
||||||
|
} else {
|
||||||
|
optionsPanel.classList.add('hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetState = () => {
|
||||||
|
files = [];
|
||||||
|
pdfjsDoc = null;
|
||||||
|
cachedBaselineData = null;
|
||||||
|
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||||
|
if (fileInput) fileInput.value = '';
|
||||||
|
updateUI();
|
||||||
|
};
|
||||||
|
|
||||||
|
async function processAllPages(): Promise<void> {
|
||||||
|
if (files.length === 0) {
|
||||||
|
showAlert('No File', 'Please upload a PDF file first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoader('Applying color adjustments...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const settings = getSettings();
|
||||||
|
const pdfBytes = (await readFileAsArrayBuffer(files[0])) as ArrayBuffer;
|
||||||
|
const doc = await getPDFDocument({ data: pdfBytes }).promise;
|
||||||
|
const newPdfDoc = await PDFDocument.create();
|
||||||
|
|
||||||
|
for (let i = 1; i <= doc.numPages; i++) {
|
||||||
|
showLoader(`Processing page ${i} of ${doc.numPages}...`);
|
||||||
|
|
||||||
|
const page = await doc.getPage(i);
|
||||||
|
const viewport = page.getViewport({ scale: 2.0 });
|
||||||
|
const renderCanvas = document.createElement('canvas');
|
||||||
|
const renderCtx = renderCanvas.getContext('2d')!;
|
||||||
|
renderCanvas.width = viewport.width;
|
||||||
|
renderCanvas.height = viewport.height;
|
||||||
|
|
||||||
|
await page.render({
|
||||||
|
canvasContext: renderCtx,
|
||||||
|
viewport,
|
||||||
|
canvas: renderCanvas,
|
||||||
|
}).promise;
|
||||||
|
|
||||||
|
const baseData = renderCtx.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
renderCanvas.width,
|
||||||
|
renderCanvas.height
|
||||||
|
);
|
||||||
|
|
||||||
|
const outputCanvas = document.createElement('canvas');
|
||||||
|
applyEffects(baseData, outputCanvas, settings);
|
||||||
|
|
||||||
|
const pngBlob = await new Promise<Blob | null>((resolve) =>
|
||||||
|
outputCanvas.toBlob(resolve, 'image/png')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pngBlob) {
|
||||||
|
const pngBytes = await pngBlob.arrayBuffer();
|
||||||
|
const pngImage = await newPdfDoc.embedPng(pngBytes);
|
||||||
|
const origViewport = page.getViewport({ scale: 1.0 });
|
||||||
|
const newPage = newPdfDoc.addPage([
|
||||||
|
origViewport.width,
|
||||||
|
origViewport.height,
|
||||||
|
]);
|
||||||
|
newPage.drawImage(pngImage, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: origViewport.width,
|
||||||
|
height: origViewport.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultBytes = await newPdfDoc.save();
|
||||||
|
downloadFile(
|
||||||
|
new Blob([new Uint8Array(resultBytes)], { type: 'application/pdf' }),
|
||||||
|
'color-adjusted.pdf'
|
||||||
|
);
|
||||||
|
showAlert(
|
||||||
|
'Success',
|
||||||
|
'Color adjustments applied successfully!',
|
||||||
|
'success',
|
||||||
|
() => {
|
||||||
|
resetState();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
showAlert(
|
||||||
|
'Error',
|
||||||
|
'Failed to apply color adjustments. The file might be corrupted.'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sliderDefaults: {
|
||||||
|
id: string;
|
||||||
|
display: string;
|
||||||
|
suffix: string;
|
||||||
|
defaultValue: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
id: 'setting-brightness',
|
||||||
|
display: 'brightness-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-contrast',
|
||||||
|
display: 'contrast-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-saturation',
|
||||||
|
display: 'saturation-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-hue-shift',
|
||||||
|
display: 'hue-shift-value',
|
||||||
|
suffix: '°',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-temperature',
|
||||||
|
display: 'temperature-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{ id: 'setting-tint', display: 'tint-value', suffix: '', defaultValue: '0' },
|
||||||
|
{
|
||||||
|
id: 'setting-gamma',
|
||||||
|
display: 'gamma-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '1.0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-sepia',
|
||||||
|
display: 'sepia-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function resetSettings(): void {
|
||||||
|
sliderDefaults.forEach(({ id, display, suffix, defaultValue }) => {
|
||||||
|
const slider = document.getElementById(id) as HTMLInputElement;
|
||||||
|
const label = document.getElementById(display);
|
||||||
|
if (slider) slider.value = defaultValue;
|
||||||
|
if (label) label.textContent = defaultValue + suffix;
|
||||||
|
});
|
||||||
|
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupSettingsListeners(): void {
|
||||||
|
sliderDefaults.forEach(({ id, display, suffix }) => {
|
||||||
|
const slider = document.getElementById(id) as HTMLInputElement;
|
||||||
|
const label = document.getElementById(display);
|
||||||
|
if (slider && label) {
|
||||||
|
slider.addEventListener('input', () => {
|
||||||
|
label.textContent = slider.value + suffix;
|
||||||
|
updatePreview();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const resetBtn = document.getElementById('reset-settings-btn');
|
||||||
|
if (resetBtn) {
|
||||||
|
resetBtn.addEventListener('click', resetSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||||
|
const dropZone = document.getElementById('drop-zone');
|
||||||
|
const processBtn = document.getElementById('process-btn');
|
||||||
|
const backBtn = document.getElementById('back-to-tools');
|
||||||
|
|
||||||
|
if (backBtn) {
|
||||||
|
backBtn.addEventListener('click', () => {
|
||||||
|
window.location.href = import.meta.env.BASE_URL;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileSelect = async (newFiles: FileList | null) => {
|
||||||
|
if (!newFiles || newFiles.length === 0) return;
|
||||||
|
const validFiles = Array.from(newFiles).filter(
|
||||||
|
(file) => file.type === 'application/pdf'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (validFiles.length === 0) {
|
||||||
|
showAlert('Invalid File', 'Please upload a PDF file.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
files = [validFiles[0]];
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
showLoader('Loading preview...');
|
||||||
|
try {
|
||||||
|
const buffer = await readFileAsArrayBuffer(validFiles[0]);
|
||||||
|
pdfjsDoc = await getPDFDocument({ data: buffer }).promise;
|
||||||
|
await renderPreview();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
showAlert('Error', 'Failed to load PDF for preview.');
|
||||||
|
} finally {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fileInput && dropZone) {
|
||||||
|
fileInput.addEventListener('change', (e) => {
|
||||||
|
handleFileSelect((e.target as HTMLInputElement).files);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropZone.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropZone.classList.add('bg-gray-700');
|
||||||
|
});
|
||||||
|
|
||||||
|
dropZone.addEventListener('dragleave', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropZone.classList.remove('bg-gray-700');
|
||||||
|
});
|
||||||
|
|
||||||
|
dropZone.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropZone.classList.remove('bg-gray-700');
|
||||||
|
handleFileSelect(e.dataTransfer?.files ?? null);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener('click', () => {
|
||||||
|
fileInput.value = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processBtn) {
|
||||||
|
processBtn.addEventListener('click', processAllPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupSettingsListeners();
|
||||||
|
});
|
||||||
586
src/js/logic/scanner-effect-page.ts
Normal file
586
src/js/logic/scanner-effect-page.ts
Normal file
@@ -0,0 +1,586 @@
|
|||||||
|
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||||
|
import {
|
||||||
|
downloadFile,
|
||||||
|
formatBytes,
|
||||||
|
readFileAsArrayBuffer,
|
||||||
|
getPDFDocument,
|
||||||
|
} from '../utils/helpers.js';
|
||||||
|
import { createIcons, icons } from 'lucide';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
|
import type { ScanSettings } from '../types/scanner-effect-type.js';
|
||||||
|
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
|
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||||
|
import.meta.url
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
let files: File[] = [];
|
||||||
|
let cachedBaselineData: ImageData | null = null;
|
||||||
|
let cachedBaselineWidth = 0;
|
||||||
|
let cachedBaselineHeight = 0;
|
||||||
|
let pdfjsDoc: pdfjsLib.PDFDocumentProxy | null = null;
|
||||||
|
|
||||||
|
function getSettings(): ScanSettings {
|
||||||
|
return {
|
||||||
|
grayscale:
|
||||||
|
(document.getElementById('setting-grayscale') as HTMLInputElement)
|
||||||
|
?.checked ?? false,
|
||||||
|
border:
|
||||||
|
(document.getElementById('setting-border') as HTMLInputElement)
|
||||||
|
?.checked ?? false,
|
||||||
|
rotate: parseFloat(
|
||||||
|
(document.getElementById('setting-rotate') as HTMLInputElement)?.value ??
|
||||||
|
'0'
|
||||||
|
),
|
||||||
|
rotateVariance: parseFloat(
|
||||||
|
(document.getElementById('setting-rotate-variance') as HTMLInputElement)
|
||||||
|
?.value ?? '0'
|
||||||
|
),
|
||||||
|
brightness: parseInt(
|
||||||
|
(document.getElementById('setting-brightness') as HTMLInputElement)
|
||||||
|
?.value ?? '0'
|
||||||
|
),
|
||||||
|
contrast: parseInt(
|
||||||
|
(document.getElementById('setting-contrast') as HTMLInputElement)
|
||||||
|
?.value ?? '0'
|
||||||
|
),
|
||||||
|
blur: parseFloat(
|
||||||
|
(document.getElementById('setting-blur') as HTMLInputElement)?.value ??
|
||||||
|
'0'
|
||||||
|
),
|
||||||
|
noise: parseInt(
|
||||||
|
(document.getElementById('setting-noise') as HTMLInputElement)?.value ??
|
||||||
|
'10'
|
||||||
|
),
|
||||||
|
yellowish: parseInt(
|
||||||
|
(document.getElementById('setting-yellowish') as HTMLInputElement)
|
||||||
|
?.value ?? '0'
|
||||||
|
),
|
||||||
|
resolution: parseInt(
|
||||||
|
(document.getElementById('setting-resolution') as HTMLInputElement)
|
||||||
|
?.value ?? '150'
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyEffects(
|
||||||
|
sourceData: ImageData,
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
settings: ScanSettings,
|
||||||
|
rotationAngle: number,
|
||||||
|
scale: number = 1
|
||||||
|
): void {
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
const w = sourceData.width;
|
||||||
|
const h = sourceData.height;
|
||||||
|
|
||||||
|
const scaledBlur = settings.blur * scale;
|
||||||
|
const scaledNoise = settings.noise * scale;
|
||||||
|
|
||||||
|
const workCanvas = document.createElement('canvas');
|
||||||
|
workCanvas.width = w;
|
||||||
|
workCanvas.height = h;
|
||||||
|
const workCtx = workCanvas.getContext('2d')!;
|
||||||
|
|
||||||
|
if (scaledBlur > 0) {
|
||||||
|
workCtx.filter = `blur(${scaledBlur}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
workCtx.putImageData(sourceData, 0, 0);
|
||||||
|
if (scaledBlur > 0) {
|
||||||
|
const tempCanvas = document.createElement('canvas');
|
||||||
|
tempCanvas.width = w;
|
||||||
|
tempCanvas.height = h;
|
||||||
|
const tempCtx = tempCanvas.getContext('2d')!;
|
||||||
|
tempCtx.filter = `blur(${scaledBlur}px)`;
|
||||||
|
tempCtx.drawImage(workCanvas, 0, 0);
|
||||||
|
workCtx.filter = 'none';
|
||||||
|
workCtx.clearRect(0, 0, w, h);
|
||||||
|
workCtx.drawImage(tempCanvas, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageData = workCtx.getImageData(0, 0, w, h);
|
||||||
|
const data = imageData.data;
|
||||||
|
|
||||||
|
const contrastFactor =
|
||||||
|
settings.contrast !== 0
|
||||||
|
? (259 * (settings.contrast + 255)) / (255 * (259 - settings.contrast))
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
|
let r = data[i];
|
||||||
|
let g = data[i + 1];
|
||||||
|
let b = data[i + 2];
|
||||||
|
|
||||||
|
if (settings.grayscale) {
|
||||||
|
const grey = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
|
||||||
|
r = grey;
|
||||||
|
g = grey;
|
||||||
|
b = grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.brightness !== 0) {
|
||||||
|
r += settings.brightness;
|
||||||
|
g += settings.brightness;
|
||||||
|
b += settings.brightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.contrast !== 0) {
|
||||||
|
r = contrastFactor * (r - 128) + 128;
|
||||||
|
g = contrastFactor * (g - 128) + 128;
|
||||||
|
b = contrastFactor * (b - 128) + 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.yellowish > 0) {
|
||||||
|
const intensity = settings.yellowish / 50;
|
||||||
|
r += 20 * intensity;
|
||||||
|
g += 12 * intensity;
|
||||||
|
b -= 15 * intensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scaledNoise > 0) {
|
||||||
|
const n = (Math.random() - 0.5) * scaledNoise;
|
||||||
|
r += n;
|
||||||
|
g += n;
|
||||||
|
b += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[i] = Math.max(0, Math.min(255, r));
|
||||||
|
data[i + 1] = Math.max(0, Math.min(255, g));
|
||||||
|
data[i + 2] = Math.max(0, Math.min(255, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
workCtx.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
|
if (settings.border) {
|
||||||
|
const borderSize = Math.max(w, h) * 0.02;
|
||||||
|
const gradient1 = workCtx.createLinearGradient(0, 0, borderSize, 0);
|
||||||
|
gradient1.addColorStop(0, 'rgba(0,0,0,0.3)');
|
||||||
|
gradient1.addColorStop(1, 'rgba(0,0,0,0)');
|
||||||
|
workCtx.fillStyle = gradient1;
|
||||||
|
workCtx.fillRect(0, 0, borderSize, h);
|
||||||
|
|
||||||
|
const gradient2 = workCtx.createLinearGradient(w, 0, w - borderSize, 0);
|
||||||
|
gradient2.addColorStop(0, 'rgba(0,0,0,0.3)');
|
||||||
|
gradient2.addColorStop(1, 'rgba(0,0,0,0)');
|
||||||
|
workCtx.fillStyle = gradient2;
|
||||||
|
workCtx.fillRect(w - borderSize, 0, borderSize, h);
|
||||||
|
|
||||||
|
const gradient3 = workCtx.createLinearGradient(0, 0, 0, borderSize);
|
||||||
|
gradient3.addColorStop(0, 'rgba(0,0,0,0.3)');
|
||||||
|
gradient3.addColorStop(1, 'rgba(0,0,0,0)');
|
||||||
|
workCtx.fillStyle = gradient3;
|
||||||
|
workCtx.fillRect(0, 0, w, borderSize);
|
||||||
|
|
||||||
|
const gradient4 = workCtx.createLinearGradient(0, h, 0, h - borderSize);
|
||||||
|
gradient4.addColorStop(0, 'rgba(0,0,0,0.3)');
|
||||||
|
gradient4.addColorStop(1, 'rgba(0,0,0,0)');
|
||||||
|
workCtx.fillStyle = gradient4;
|
||||||
|
workCtx.fillRect(0, h - borderSize, w, borderSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rotationAngle !== 0) {
|
||||||
|
const rad = (rotationAngle * Math.PI) / 180;
|
||||||
|
const cos = Math.abs(Math.cos(rad));
|
||||||
|
const sin = Math.abs(Math.sin(rad));
|
||||||
|
const newW = Math.ceil(w * cos + h * sin);
|
||||||
|
const newH = Math.ceil(w * sin + h * cos);
|
||||||
|
|
||||||
|
canvas.width = newW;
|
||||||
|
canvas.height = newH;
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.fillRect(0, 0, newW, newH);
|
||||||
|
ctx.translate(newW / 2, newH / 2);
|
||||||
|
ctx.rotate(rad);
|
||||||
|
ctx.drawImage(workCanvas, -w / 2, -h / 2);
|
||||||
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
} else {
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
ctx.drawImage(workCanvas, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePreview(): void {
|
||||||
|
if (!cachedBaselineData) return;
|
||||||
|
|
||||||
|
const previewCanvas = document.getElementById(
|
||||||
|
'preview-canvas'
|
||||||
|
) as HTMLCanvasElement;
|
||||||
|
if (!previewCanvas) return;
|
||||||
|
|
||||||
|
const settings = getSettings();
|
||||||
|
const baselineCopy = new ImageData(
|
||||||
|
new Uint8ClampedArray(cachedBaselineData.data),
|
||||||
|
cachedBaselineWidth,
|
||||||
|
cachedBaselineHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
applyEffects(baselineCopy, previewCanvas, settings, settings.rotate);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderPreview(): Promise<void> {
|
||||||
|
if (!pdfjsDoc) return;
|
||||||
|
|
||||||
|
const page = await pdfjsDoc.getPage(1);
|
||||||
|
const viewport = page.getViewport({ scale: 1.0 });
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
|
||||||
|
await page.render({ canvasContext: ctx, viewport, canvas }).promise;
|
||||||
|
|
||||||
|
cachedBaselineData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
cachedBaselineWidth = canvas.width;
|
||||||
|
cachedBaselineHeight = canvas.height;
|
||||||
|
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUI = () => {
|
||||||
|
const fileDisplayArea = document.getElementById('file-display-area');
|
||||||
|
const optionsPanel = document.getElementById('options-panel');
|
||||||
|
|
||||||
|
if (!fileDisplayArea || !optionsPanel) return;
|
||||||
|
|
||||||
|
fileDisplayArea.innerHTML = '';
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
optionsPanel.classList.remove('hidden');
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
const fileDiv = document.createElement('div');
|
||||||
|
fileDiv.className =
|
||||||
|
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||||
|
|
||||||
|
const infoContainer = document.createElement('div');
|
||||||
|
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||||
|
|
||||||
|
const nameSpan = document.createElement('div');
|
||||||
|
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||||
|
nameSpan.textContent = file.name;
|
||||||
|
|
||||||
|
const metaSpan = document.createElement('div');
|
||||||
|
metaSpan.className = 'text-xs text-gray-400';
|
||||||
|
metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`;
|
||||||
|
|
||||||
|
infoContainer.append(nameSpan, metaSpan);
|
||||||
|
|
||||||
|
const removeBtn = document.createElement('button');
|
||||||
|
removeBtn.className =
|
||||||
|
'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||||
|
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||||
|
removeBtn.onclick = () => {
|
||||||
|
files = [];
|
||||||
|
pdfjsDoc = null;
|
||||||
|
cachedBaselineData = null;
|
||||||
|
updateUI();
|
||||||
|
};
|
||||||
|
|
||||||
|
fileDiv.append(infoContainer, removeBtn);
|
||||||
|
fileDisplayArea.appendChild(fileDiv);
|
||||||
|
|
||||||
|
readFileAsArrayBuffer(file)
|
||||||
|
.then((buffer: ArrayBuffer) => {
|
||||||
|
return getPDFDocument(buffer).promise;
|
||||||
|
})
|
||||||
|
.then((pdf: pdfjsLib.PDFDocumentProxy) => {
|
||||||
|
metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
metaSpan.textContent = formatBytes(file.size);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
createIcons({ icons });
|
||||||
|
} else {
|
||||||
|
optionsPanel.classList.add('hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetState = () => {
|
||||||
|
files = [];
|
||||||
|
pdfjsDoc = null;
|
||||||
|
cachedBaselineData = null;
|
||||||
|
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||||
|
if (fileInput) fileInput.value = '';
|
||||||
|
updateUI();
|
||||||
|
};
|
||||||
|
|
||||||
|
async function processAllPages(): Promise<void> {
|
||||||
|
if (files.length === 0) {
|
||||||
|
showAlert('No File', 'Please upload a PDF file first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoader('Applying scanner effect...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const settings = getSettings();
|
||||||
|
const pdfBytes = (await readFileAsArrayBuffer(files[0])) as ArrayBuffer;
|
||||||
|
const doc = await getPDFDocument({ data: pdfBytes }).promise;
|
||||||
|
const newPdfDoc = await PDFDocument.create();
|
||||||
|
const dpiScale = settings.resolution / 72;
|
||||||
|
|
||||||
|
for (let i = 1; i <= doc.numPages; i++) {
|
||||||
|
showLoader(`Processing page ${i} of ${doc.numPages}...`);
|
||||||
|
|
||||||
|
const page = await doc.getPage(i);
|
||||||
|
const viewport = page.getViewport({ scale: dpiScale });
|
||||||
|
const renderCanvas = document.createElement('canvas');
|
||||||
|
const renderCtx = renderCanvas.getContext('2d')!;
|
||||||
|
renderCanvas.width = viewport.width;
|
||||||
|
renderCanvas.height = viewport.height;
|
||||||
|
|
||||||
|
await page.render({
|
||||||
|
canvasContext: renderCtx,
|
||||||
|
viewport,
|
||||||
|
canvas: renderCanvas,
|
||||||
|
}).promise;
|
||||||
|
|
||||||
|
const baseData = renderCtx.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
renderCanvas.width,
|
||||||
|
renderCanvas.height
|
||||||
|
);
|
||||||
|
const baselineCopy = new ImageData(
|
||||||
|
new Uint8ClampedArray(baseData.data),
|
||||||
|
baseData.width,
|
||||||
|
baseData.height
|
||||||
|
);
|
||||||
|
|
||||||
|
const outputCanvas = document.createElement('canvas');
|
||||||
|
const pageRotation =
|
||||||
|
settings.rotate +
|
||||||
|
(settings.rotateVariance > 0
|
||||||
|
? (Math.random() - 0.5) * 2 * settings.rotateVariance
|
||||||
|
: 0);
|
||||||
|
|
||||||
|
applyEffects(
|
||||||
|
baselineCopy,
|
||||||
|
outputCanvas,
|
||||||
|
settings,
|
||||||
|
pageRotation,
|
||||||
|
dpiScale
|
||||||
|
);
|
||||||
|
|
||||||
|
const jpegBlob = await new Promise<Blob | null>((resolve) =>
|
||||||
|
outputCanvas.toBlob(resolve, 'image/jpeg', 0.85)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (jpegBlob) {
|
||||||
|
const jpegBytes = await jpegBlob.arrayBuffer();
|
||||||
|
const jpegImage = await newPdfDoc.embedJpg(jpegBytes);
|
||||||
|
const newPage = newPdfDoc.addPage([
|
||||||
|
outputCanvas.width,
|
||||||
|
outputCanvas.height,
|
||||||
|
]);
|
||||||
|
newPage.drawImage(jpegImage, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: outputCanvas.width,
|
||||||
|
height: outputCanvas.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultBytes = await newPdfDoc.save();
|
||||||
|
downloadFile(
|
||||||
|
new Blob([new Uint8Array(resultBytes)], { type: 'application/pdf' }),
|
||||||
|
'scanned.pdf'
|
||||||
|
);
|
||||||
|
showAlert(
|
||||||
|
'Success',
|
||||||
|
'Scanner effect applied successfully!',
|
||||||
|
'success',
|
||||||
|
() => {
|
||||||
|
resetState();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
showAlert(
|
||||||
|
'Error',
|
||||||
|
'Failed to apply scanner effect. The file might be corrupted.'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sliderDefaults: {
|
||||||
|
id: string;
|
||||||
|
display: string;
|
||||||
|
suffix: string;
|
||||||
|
defaultValue: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
id: 'setting-rotate',
|
||||||
|
display: 'rotate-value',
|
||||||
|
suffix: '°',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-rotate-variance',
|
||||||
|
display: 'rotate-variance-value',
|
||||||
|
suffix: '°',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-brightness',
|
||||||
|
display: 'brightness-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-contrast',
|
||||||
|
display: 'contrast-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-blur',
|
||||||
|
display: 'blur-value',
|
||||||
|
suffix: 'px',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-noise',
|
||||||
|
display: 'noise-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '10',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-yellowish',
|
||||||
|
display: 'yellowish-value',
|
||||||
|
suffix: '',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-resolution',
|
||||||
|
display: 'resolution-value',
|
||||||
|
suffix: ' DPI',
|
||||||
|
defaultValue: '150',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function resetSettings(): void {
|
||||||
|
sliderDefaults.forEach(({ id, display, suffix, defaultValue }) => {
|
||||||
|
const slider = document.getElementById(id) as HTMLInputElement;
|
||||||
|
const label = document.getElementById(display);
|
||||||
|
if (slider) slider.value = defaultValue;
|
||||||
|
if (label) label.textContent = defaultValue + suffix;
|
||||||
|
});
|
||||||
|
|
||||||
|
const grayscale = document.getElementById(
|
||||||
|
'setting-grayscale'
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const border = document.getElementById('setting-border') as HTMLInputElement;
|
||||||
|
if (grayscale) grayscale.checked = false;
|
||||||
|
if (border) border.checked = false;
|
||||||
|
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupSettingsListeners(): void {
|
||||||
|
sliderDefaults.forEach(({ id, display, suffix }) => {
|
||||||
|
const slider = document.getElementById(id) as HTMLInputElement;
|
||||||
|
const label = document.getElementById(display);
|
||||||
|
if (slider && label) {
|
||||||
|
slider.addEventListener('input', () => {
|
||||||
|
label.textContent = slider.value + suffix;
|
||||||
|
if (id !== 'setting-resolution') {
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleIds = ['setting-grayscale', 'setting-border'];
|
||||||
|
toggleIds.forEach((id) => {
|
||||||
|
const toggle = document.getElementById(id) as HTMLInputElement;
|
||||||
|
if (toggle) {
|
||||||
|
toggle.addEventListener('change', updatePreview);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const resetBtn = document.getElementById('reset-settings-btn');
|
||||||
|
if (resetBtn) {
|
||||||
|
resetBtn.addEventListener('click', resetSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||||
|
const dropZone = document.getElementById('drop-zone');
|
||||||
|
const processBtn = document.getElementById('process-btn');
|
||||||
|
const backBtn = document.getElementById('back-to-tools');
|
||||||
|
|
||||||
|
if (backBtn) {
|
||||||
|
backBtn.addEventListener('click', () => {
|
||||||
|
window.location.href = import.meta.env.BASE_URL;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileSelect = async (newFiles: FileList | null) => {
|
||||||
|
if (!newFiles || newFiles.length === 0) return;
|
||||||
|
const validFiles = Array.from(newFiles).filter(
|
||||||
|
(file) => file.type === 'application/pdf'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (validFiles.length === 0) {
|
||||||
|
showAlert('Invalid File', 'Please upload a PDF file.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
files = [validFiles[0]];
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
showLoader('Loading preview...');
|
||||||
|
try {
|
||||||
|
const buffer = await readFileAsArrayBuffer(validFiles[0]);
|
||||||
|
pdfjsDoc = await getPDFDocument({ data: buffer }).promise;
|
||||||
|
await renderPreview();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
showAlert('Error', 'Failed to load PDF for preview.');
|
||||||
|
} finally {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fileInput && dropZone) {
|
||||||
|
fileInput.addEventListener('change', (e) => {
|
||||||
|
handleFileSelect((e.target as HTMLInputElement).files);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropZone.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropZone.classList.add('bg-gray-700');
|
||||||
|
});
|
||||||
|
|
||||||
|
dropZone.addEventListener('dragleave', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropZone.classList.remove('bg-gray-700');
|
||||||
|
});
|
||||||
|
|
||||||
|
dropZone.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropZone.classList.remove('bg-gray-700');
|
||||||
|
handleFileSelect(e.dataTransfer?.files ?? null);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener('click', () => {
|
||||||
|
fileInput.value = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processBtn) {
|
||||||
|
processBtn.addEventListener('click', processAllPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupSettingsListeners();
|
||||||
|
});
|
||||||
10
src/js/types/adjust-colors-type.ts
Normal file
10
src/js/types/adjust-colors-type.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export interface AdjustColorsSettings {
|
||||||
|
brightness: number;
|
||||||
|
contrast: number;
|
||||||
|
saturation: number;
|
||||||
|
hueShift: number;
|
||||||
|
temperature: number;
|
||||||
|
tint: number;
|
||||||
|
gamma: number;
|
||||||
|
sepia: number;
|
||||||
|
}
|
||||||
@@ -47,3 +47,5 @@ export * from './sign-pdf-type.ts';
|
|||||||
export * from './add-watermark-type.ts';
|
export * from './add-watermark-type.ts';
|
||||||
export * from './email-to-pdf-type.ts';
|
export * from './email-to-pdf-type.ts';
|
||||||
export * from './bookmark-pdf-type.ts';
|
export * from './bookmark-pdf-type.ts';
|
||||||
|
export * from './scanner-effect-type.ts';
|
||||||
|
export * from './adjust-colors-type.ts';
|
||||||
|
|||||||
16
src/js/types/scanner-effect-type.ts
Normal file
16
src/js/types/scanner-effect-type.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export interface ScannerEffectState {
|
||||||
|
file: File | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScanSettings {
|
||||||
|
grayscale: boolean;
|
||||||
|
border: boolean;
|
||||||
|
rotate: number;
|
||||||
|
rotateVariance: number;
|
||||||
|
brightness: number;
|
||||||
|
contrast: number;
|
||||||
|
blur: number;
|
||||||
|
noise: number;
|
||||||
|
yellowish: number;
|
||||||
|
resolution: number;
|
||||||
|
}
|
||||||
599
src/pages/adjust-colors.html
Normal file
599
src/pages/adjust-colors.html
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<title>
|
||||||
|
Adjust Colors Online Free - PDF Color Adjustment Tool | BentoPDF
|
||||||
|
</title>
|
||||||
|
<meta
|
||||||
|
name="title"
|
||||||
|
content="Adjust Colors Online Free - PDF Color Adjustment Tool | BentoPDF"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="★ Adjust PDF colors online free - Brightness, contrast, saturation, hue, temperature & more ★ No signup ★ Unlimited files ★ Privacy-first ★ Works in browser ★ Fast & secure"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content="adjust pdf colors, pdf brightness, pdf contrast, pdf saturation, pdf hue shift, pdf color correction"
|
||||||
|
/>
|
||||||
|
<meta name="author" content="BentoPDF" />
|
||||||
|
<meta
|
||||||
|
name="robots"
|
||||||
|
content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link rel="canonical" href="https://www.bentopdf.com/adjust-colors.html" />
|
||||||
|
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="https://www.bentopdf.com/adjust-colors" />
|
||||||
|
<meta
|
||||||
|
property="og:title"
|
||||||
|
content="Adjust Colors Online Free - PDF Color Adjustment Tool | BentoPDF"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="★ Adjust PDF colors online free - Brightness, contrast, saturation, hue, temperature & more ★ No signup ★ Unlimited files ★ Privacy-first ★ Works in browser ★ Fast & secure"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://www.bentopdf.com/images/og-adjust-colors.png"
|
||||||
|
/>
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta property="og:site_name" content="BentoPDF" />
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:url" content="https://www.bentopdf.com/adjust-colors" />
|
||||||
|
<meta name="twitter:title" content="Adjust Colors Free" />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="★ Adjust PDF colors online free - Brightness, contrast, saturation, hue & more ★ No signup ★ Unlimited files ★ Privacy-first ★ Works in browser"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="twitter:image"
|
||||||
|
content="https://www.bentopdf.com/images/twitter-adjust-colors.png"
|
||||||
|
/>
|
||||||
|
<meta name="twitter:site" content="@BentoPDF" />
|
||||||
|
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Adjust Colors" />
|
||||||
|
|
||||||
|
<title>Adjust Colors - BentoPDF</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Adjust brightness, contrast, saturation, hue, temperature, gamma and more in your PDF. Free, secure, and runs entirely in your browser."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="192x192"
|
||||||
|
href="/images/favicon-192x192.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="512x512"
|
||||||
|
href="/images/favicon-512x512.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="/images/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="antialiased bg-gray-900">
|
||||||
|
{{> navbar }}
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="uploader"
|
||||||
|
class="min-h-screen flex flex-col items-center justify-start py-12 p-4 bg-gray-900"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="tool-uploader"
|
||||||
|
class="bg-gray-800 rounded-xl shadow-xl px-4 py-8 md:p-8 max-w-4xl w-full text-gray-200 border border-gray-700"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
id="back-to-tools"
|
||||||
|
class="flex items-center gap-2 text-indigo-400 hover:text-indigo-300 mb-6 font-semibold"
|
||||||
|
>
|
||||||
|
<i data-lucide="arrow-left" class="cursor-pointer"></i>
|
||||||
|
<span class="cursor-pointer" data-i18n="tools.backToTools">
|
||||||
|
Back to Tools
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h1
|
||||||
|
class="text-2xl font-bold text-white mb-2"
|
||||||
|
data-i18n="tools:adjustColors.name"
|
||||||
|
>
|
||||||
|
Adjust Colors Free Online - PDF Color Correction Tool
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-400 mb-6" data-i18n="tools:adjustColors.subtitle">
|
||||||
|
Fine-tune brightness, contrast, saturation, hue and more in your PDF.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="drop-zone"
|
||||||
|
class="relative flex flex-col items-center justify-center w-full h-48 md:h-64 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-900 hover:bg-gray-700 transition-colors duration-300"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
||||||
|
<i
|
||||||
|
data-lucide="upload-cloud"
|
||||||
|
class="w-10 h-10 mb-3 text-gray-400"
|
||||||
|
></i>
|
||||||
|
<p class="mb-2 text-sm text-gray-400">
|
||||||
|
<span class="font-semibold" data-i18n="upload.clickToSelect"
|
||||||
|
>Click to select a file</span
|
||||||
|
>
|
||||||
|
<span data-i18n="upload.orDragAndDrop">or drag and drop</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-500">A single PDF file</p>
|
||||||
|
<p class="text-xs text-gray-500" data-i18n="upload.filesNeverLeave">
|
||||||
|
Your files never leave your device.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="file-input"
|
||||||
|
type="file"
|
||||||
|
class="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
|
||||||
|
accept="application/pdf"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="file-display-area" class="mt-4 space-y-2"></div>
|
||||||
|
|
||||||
|
<div id="options-panel" class="hidden mt-6">
|
||||||
|
<div class="flex flex-col lg:flex-row gap-6">
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div
|
||||||
|
class="border border-gray-600 rounded-lg overflow-hidden bg-gray-900"
|
||||||
|
>
|
||||||
|
<canvas id="preview-canvas" class="w-full"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lg:w-80 flex-shrink-0 space-y-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h3
|
||||||
|
class="text-lg font-semibold text-white flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<i data-lucide="palette" class="w-5 h-5"></i>
|
||||||
|
<span data-i18n="tools:adjustColors.colorSettings"
|
||||||
|
>Color Settings</span
|
||||||
|
>
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
id="reset-settings-btn"
|
||||||
|
class="text-xs text-gray-300 hover:text-white border border-gray-600 hover:border-gray-500 px-3 py-1 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:adjustColors.brightness"
|
||||||
|
>Brightness</span
|
||||||
|
>
|
||||||
|
<span id="brightness-value" class="text-gray-400">0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-brightness"
|
||||||
|
min="-100"
|
||||||
|
max="100"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:adjustColors.contrast"
|
||||||
|
>Contrast</span
|
||||||
|
>
|
||||||
|
<span id="contrast-value" class="text-gray-400">0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-contrast"
|
||||||
|
min="-100"
|
||||||
|
max="100"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:adjustColors.saturation"
|
||||||
|
>Saturation</span
|
||||||
|
>
|
||||||
|
<span id="saturation-value" class="text-gray-400">0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-saturation"
|
||||||
|
min="-100"
|
||||||
|
max="100"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:adjustColors.hueShift"
|
||||||
|
>Hue Shift</span
|
||||||
|
>
|
||||||
|
<span id="hue-shift-value" class="text-gray-400">0°</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-hue-shift"
|
||||||
|
min="0"
|
||||||
|
max="360"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:adjustColors.temperature"
|
||||||
|
>Temperature</span
|
||||||
|
>
|
||||||
|
<span id="temperature-value" class="text-gray-400">0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-temperature"
|
||||||
|
min="-50"
|
||||||
|
max="50"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:adjustColors.tint"
|
||||||
|
>Tint</span
|
||||||
|
>
|
||||||
|
<span id="tint-value" class="text-gray-400">0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-tint"
|
||||||
|
min="-50"
|
||||||
|
max="50"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:adjustColors.gamma"
|
||||||
|
>Gamma</span
|
||||||
|
>
|
||||||
|
<span id="gamma-value" class="text-gray-400">1.0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-gamma"
|
||||||
|
min="0.2"
|
||||||
|
max="5.0"
|
||||||
|
value="1.0"
|
||||||
|
step="0.1"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:adjustColors.sepia"
|
||||||
|
>Sepia</span
|
||||||
|
>
|
||||||
|
<span id="sepia-value" class="text-gray-400">0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-sepia"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="process-btn"
|
||||||
|
class="btn-gradient w-full mt-6"
|
||||||
|
data-i18n="tools:adjustColors.processButton"
|
||||||
|
>
|
||||||
|
Apply Color Adjustments
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="loader-modal"
|
||||||
|
class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-gray-800 p-8 rounded-lg flex flex-col items-center gap-4 border border-gray-700 shadow-xl"
|
||||||
|
>
|
||||||
|
<div class="solid-spinner"></div>
|
||||||
|
<p id="loader-text" class="text-white text-lg font-medium">
|
||||||
|
Processing...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="alert-modal"
|
||||||
|
class="fixed inset-0 bg-gray-900 bg-opacity-90 flex items-center justify-center z-50 hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-gray-800 rounded-lg shadow-xl p-6 max-w-sm w-full border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
id="alert-title"
|
||||||
|
class="text-xl font-bold text-white mb-2"
|
||||||
|
data-i18n="alert.title"
|
||||||
|
>
|
||||||
|
Alert
|
||||||
|
</h3>
|
||||||
|
<p id="alert-message" class="text-gray-300 mb-6"></p>
|
||||||
|
<button
|
||||||
|
id="alert-ok"
|
||||||
|
class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 px-4 rounded-lg transition-colors duration-200"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="max-w-4xl mx-auto px-4 py-12">
|
||||||
|
<h2 class="text-2xl md:text-3xl font-bold text-white mb-8 text-center">
|
||||||
|
How It Works
|
||||||
|
</h2>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
class="flex-shrink-0 w-10 h-10 bg-indigo-600 rounded-full flex items-center justify-center text-white font-bold"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-1">Upload File</h3>
|
||||||
|
<p class="text-gray-400">
|
||||||
|
Click or drag and drop your PDF file to begin
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
class="flex-shrink-0 w-10 h-10 bg-indigo-600 rounded-full flex items-center justify-center text-white font-bold"
|
||||||
|
>
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-1">Adjust Colors</h3>
|
||||||
|
<p class="text-gray-400">
|
||||||
|
Fine-tune color settings with real-time preview
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
class="flex-shrink-0 w-10 h-10 bg-indigo-600 rounded-full flex items-center justify-center text-white font-bold"
|
||||||
|
>
|
||||||
|
3
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-1">Download</h3>
|
||||||
|
<p class="text-gray-400">Save your color-adjusted PDF instantly</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="max-w-6xl mx-auto px-4 py-12">
|
||||||
|
<h2 class="text-2xl md:text-3xl font-bold text-white mb-6 text-center">
|
||||||
|
Related PDF Tools
|
||||||
|
</h2>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||||
|
<a
|
||||||
|
href="pdf-to-greyscale.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">PDF to Greyscale</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Free online greyscale tool</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="invert-colors.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">Invert Colors</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Free online invert colors tool</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="scanner-effect.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">Scanner Effect</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Make PDFs look scanned</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="background-color.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">Background Color</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Change PDF background</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="change-text-color.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">Text Color</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Change PDF text color</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="max-w-4xl mx-auto px-4 py-12">
|
||||||
|
<h2 class="text-2xl md:text-3xl font-bold text-white mb-6 text-center">
|
||||||
|
Frequently Asked Questions
|
||||||
|
</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<details class="bg-gray-800 p-5 rounded-lg border border-gray-700">
|
||||||
|
<summary
|
||||||
|
class="cursor-pointer font-semibold text-white flex items-center justify-between"
|
||||||
|
>
|
||||||
|
Is adjust colors really free?
|
||||||
|
<i data-lucide="chevron-down" class="w-5 h-5"></i>
|
||||||
|
</summary>
|
||||||
|
<p class="mt-3 text-gray-400">
|
||||||
|
Yes! BentoPDF is 100% free with no hidden fees, no signup required,
|
||||||
|
and unlimited file processing.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
<details class="bg-gray-800 p-5 rounded-lg border border-gray-700">
|
||||||
|
<summary
|
||||||
|
class="cursor-pointer font-semibold text-white flex items-center justify-between"
|
||||||
|
>
|
||||||
|
Are my files private and secure?
|
||||||
|
<i data-lucide="chevron-down" class="w-5 h-5"></i>
|
||||||
|
</summary>
|
||||||
|
<p class="mt-3 text-gray-400">
|
||||||
|
Absolutely! All processing happens in your browser. Your files never
|
||||||
|
leave your device, ensuring complete privacy.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
<details class="bg-gray-800 p-5 rounded-lg border border-gray-700">
|
||||||
|
<summary
|
||||||
|
class="cursor-pointer font-semibold text-white flex items-center justify-between"
|
||||||
|
>
|
||||||
|
Is there a file size limit?
|
||||||
|
<i data-lucide="chevron-down" class="w-5 h-5"></i>
|
||||||
|
</summary>
|
||||||
|
<p class="mt-3 text-gray-400">
|
||||||
|
No! Process files of any size, as many times as you want, completely
|
||||||
|
free.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{> footer }}
|
||||||
|
<script type="module" src="/src/js/utils/lucide-init.ts"></script>
|
||||||
|
<script type="module" src="/src/js/utils/full-width.ts"></script>
|
||||||
|
<script type="module" src="/src/js/utils/simple-mode-footer.ts"></script>
|
||||||
|
<script type="module" src="/src/version.ts"></script>
|
||||||
|
<script type="module" src="/src/js/logic/adjust-colors-page.ts"></script>
|
||||||
|
<script type="module" src="/src/js/mobileMenu.ts"></script>
|
||||||
|
<script type="module" src="/src/js/main.ts"></script>
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "SoftwareApplication",
|
||||||
|
"name": "Adjust Colors - BentoPDF",
|
||||||
|
"applicationCategory": "PDF Tool",
|
||||||
|
"operatingSystem": "Any - Web Browser",
|
||||||
|
"offers": {
|
||||||
|
"@type": "Offer",
|
||||||
|
"price": "0",
|
||||||
|
"priceCurrency": "USD"
|
||||||
|
},
|
||||||
|
"aggregateRating": {
|
||||||
|
"@type": "AggregateRating",
|
||||||
|
"ratingValue": "4.7",
|
||||||
|
"ratingCount": "1856"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "HowTo",
|
||||||
|
"name": "How to adjust PDF colors online",
|
||||||
|
"description": "Learn how to adjust PDF colors using BentoPDF",
|
||||||
|
"step": [
|
||||||
|
{
|
||||||
|
"@type": "HowToStep",
|
||||||
|
"position": 1,
|
||||||
|
"name": "Upload File",
|
||||||
|
"text": "Click or drag and drop your PDF file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "HowToStep",
|
||||||
|
"position": 2,
|
||||||
|
"name": "Adjust Colors",
|
||||||
|
"text": "Fine-tune brightness, contrast, saturation, hue and more with live preview"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "HowToStep",
|
||||||
|
"position": 3,
|
||||||
|
"name": "Download",
|
||||||
|
"text": "Download your color-adjusted PDF"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "BreadcrumbList",
|
||||||
|
"itemListElement": [
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": 1,
|
||||||
|
"name": "Home",
|
||||||
|
"item": "https://www.bentopdf.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": 2,
|
||||||
|
"name": "Adjust Colors",
|
||||||
|
"item": "https://www.bentopdf.com/adjust-colors"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
659
src/pages/scanner-effect.html
Normal file
659
src/pages/scanner-effect.html
Normal file
@@ -0,0 +1,659 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<title>Scanner Effect Online Free - Scanner Effect Tool | BentoPDF</title>
|
||||||
|
<meta
|
||||||
|
name="title"
|
||||||
|
content="Scanner Effect Online Free - Scanner Effect Tool | BentoPDF"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="★ Scanner Effect online free - Make PDFs look scanned ★ No signup ★ Unlimited files ★ Privacy-first ★ Works in browser ★ Fast & secure"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content="scanner effect, scan pdf, make pdf look scanned, pdf scanner"
|
||||||
|
/>
|
||||||
|
<meta name="author" content="BentoPDF" />
|
||||||
|
<meta
|
||||||
|
name="robots"
|
||||||
|
content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link rel="canonical" href="https://www.bentopdf.com/scanner-effect.html" />
|
||||||
|
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="https://www.bentopdf.com/scanner-effect" />
|
||||||
|
<meta
|
||||||
|
property="og:title"
|
||||||
|
content="Scanner Effect Online Free - Scanner Effect Tool | BentoPDF"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="★ Scanner Effect online free - Make PDFs look scanned ★ No signup ★ Unlimited files ★ Privacy-first ★ Works in browser ★ Fast & secure"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://www.bentopdf.com/images/og-scanner-effect.png"
|
||||||
|
/>
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta property="og:site_name" content="BentoPDF" />
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta
|
||||||
|
name="twitter:url"
|
||||||
|
content="https://www.bentopdf.com/scanner-effect"
|
||||||
|
/>
|
||||||
|
<meta name="twitter:title" content="Scanner Effect Free" />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="★ Scanner Effect online free - Make PDFs look scanned ★ No signup ★ Unlimited files ★ Privacy-first ★ Works in browser ★ Fast & secu"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="twitter:image"
|
||||||
|
content="https://www.bentopdf.com/images/twitter-scanner-effect.png"
|
||||||
|
/>
|
||||||
|
<meta name="twitter:site" content="@BentoPDF" />
|
||||||
|
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Scanner Effect" />
|
||||||
|
|
||||||
|
<title>Scanner Effect - BentoPDF</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Make your PDF look like a scanned document. Adjust noise, brightness, contrast, blur, and more. Free, secure, and runs entirely in your browser."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="192x192"
|
||||||
|
href="/images/favicon-192x192.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="512x512"
|
||||||
|
href="/images/favicon-512x512.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="/images/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="antialiased bg-gray-900">
|
||||||
|
{{> navbar }}
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="uploader"
|
||||||
|
class="min-h-screen flex flex-col items-center justify-start py-12 p-4 bg-gray-900"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="tool-uploader"
|
||||||
|
class="bg-gray-800 rounded-xl shadow-xl px-4 py-8 md:p-8 max-w-4xl w-full text-gray-200 border border-gray-700"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
id="back-to-tools"
|
||||||
|
class="flex items-center gap-2 text-indigo-400 hover:text-indigo-300 mb-6 font-semibold"
|
||||||
|
>
|
||||||
|
<i data-lucide="arrow-left" class="cursor-pointer"></i>
|
||||||
|
<span class="cursor-pointer" data-i18n="tools.backToTools">
|
||||||
|
Back to Tools
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h1
|
||||||
|
class="text-2xl font-bold text-white mb-2"
|
||||||
|
data-i18n="tools:scannerEffect.name"
|
||||||
|
>
|
||||||
|
Scanner Effect Free Online - Make PDFs Look Scanned
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-400 mb-6" data-i18n="tools:scannerEffect.subtitle">
|
||||||
|
Make your PDF look like a scanned document.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="drop-zone"
|
||||||
|
class="relative flex flex-col items-center justify-center w-full h-48 md:h-64 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-900 hover:bg-gray-700 transition-colors duration-300"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
||||||
|
<i
|
||||||
|
data-lucide="upload-cloud"
|
||||||
|
class="w-10 h-10 mb-3 text-gray-400"
|
||||||
|
></i>
|
||||||
|
<p class="mb-2 text-sm text-gray-400">
|
||||||
|
<span class="font-semibold" data-i18n="upload.clickToSelect"
|
||||||
|
>Click to select a file</span
|
||||||
|
>
|
||||||
|
<span data-i18n="upload.orDragAndDrop">or drag and drop</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-500">A single PDF file</p>
|
||||||
|
<p class="text-xs text-gray-500" data-i18n="upload.filesNeverLeave">
|
||||||
|
Your files never leave your device.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="file-input"
|
||||||
|
type="file"
|
||||||
|
class="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
|
||||||
|
accept="application/pdf"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="file-display-area" class="mt-4 space-y-2"></div>
|
||||||
|
|
||||||
|
<div id="options-panel" class="hidden mt-6">
|
||||||
|
<div class="flex flex-col lg:flex-row gap-6">
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div
|
||||||
|
class="border border-gray-600 rounded-lg overflow-hidden bg-gray-900"
|
||||||
|
>
|
||||||
|
<canvas id="preview-canvas" class="w-full"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lg:w-80 flex-shrink-0 space-y-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h3
|
||||||
|
class="text-lg font-semibold text-white flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<i data-lucide="scan" class="w-5 h-5"></i>
|
||||||
|
<span data-i18n="tools:scannerEffect.scanSettings"
|
||||||
|
>Scan Settings</span
|
||||||
|
>
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
id="reset-settings-btn"
|
||||||
|
class="text-xs text-gray-300 hover:text-white border border-gray-600 hover:border-gray-500 px-3 py-1 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span
|
||||||
|
class="text-sm text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.colorspace"
|
||||||
|
>Colorspace</span
|
||||||
|
>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<span
|
||||||
|
class="text-xs text-gray-400"
|
||||||
|
data-i18n="tools:scannerEffect.gray"
|
||||||
|
>Gray</span
|
||||||
|
>
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="setting-grayscale"
|
||||||
|
class="sr-only peer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="w-10 h-5 bg-gray-600 peer-checked:bg-indigo-600 rounded-full transition-colors"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="absolute left-0.5 top-0.5 w-4 h-4 bg-white rounded-full transition-transform peer-checked:translate-x-5"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span
|
||||||
|
class="text-sm text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.border"
|
||||||
|
>Border</span
|
||||||
|
>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="setting-border"
|
||||||
|
class="sr-only peer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="w-10 h-5 bg-gray-600 peer-checked:bg-indigo-600 rounded-full transition-colors"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="absolute left-0.5 top-0.5 w-4 h-4 bg-white rounded-full transition-transform peer-checked:translate-x-5"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.rotate"
|
||||||
|
>Rotate</span
|
||||||
|
>
|
||||||
|
<span id="rotate-value" class="text-gray-400">0°</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-rotate"
|
||||||
|
min="-5"
|
||||||
|
max="5"
|
||||||
|
value="0"
|
||||||
|
step="0.1"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.rotateVariance"
|
||||||
|
>Rotate Variance</span
|
||||||
|
>
|
||||||
|
<span id="rotate-variance-value" class="text-gray-400"
|
||||||
|
>0°</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-rotate-variance"
|
||||||
|
min="0"
|
||||||
|
max="3"
|
||||||
|
value="0"
|
||||||
|
step="0.1"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.brightness"
|
||||||
|
>Brightness</span
|
||||||
|
>
|
||||||
|
<span id="brightness-value" class="text-gray-400">0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-brightness"
|
||||||
|
min="-50"
|
||||||
|
max="50"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.contrast"
|
||||||
|
>Contrast</span
|
||||||
|
>
|
||||||
|
<span id="contrast-value" class="text-gray-400">0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-contrast"
|
||||||
|
min="-50"
|
||||||
|
max="50"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.blur"
|
||||||
|
>Blur</span
|
||||||
|
>
|
||||||
|
<span id="blur-value" class="text-gray-400">0px</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-blur"
|
||||||
|
min="0"
|
||||||
|
max="3"
|
||||||
|
value="0"
|
||||||
|
step="0.1"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.noise"
|
||||||
|
>Noise</span
|
||||||
|
>
|
||||||
|
<span id="noise-value" class="text-gray-400">10</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-noise"
|
||||||
|
min="0"
|
||||||
|
max="50"
|
||||||
|
value="10"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.yellowish"
|
||||||
|
>Yellowish</span
|
||||||
|
>
|
||||||
|
<span id="yellowish-value" class="text-gray-400">0</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-yellowish"
|
||||||
|
min="0"
|
||||||
|
max="50"
|
||||||
|
value="0"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-1">
|
||||||
|
<span
|
||||||
|
class="text-gray-300"
|
||||||
|
data-i18n="tools:scannerEffect.resolution"
|
||||||
|
>Resolution</span
|
||||||
|
>
|
||||||
|
<span id="resolution-value" class="text-gray-400"
|
||||||
|
>150 DPI</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="setting-resolution"
|
||||||
|
min="72"
|
||||||
|
max="300"
|
||||||
|
value="150"
|
||||||
|
class="w-full accent-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="process-btn"
|
||||||
|
class="btn-gradient w-full mt-6"
|
||||||
|
data-i18n="tools:scannerEffect.processButton"
|
||||||
|
>
|
||||||
|
Apply Scanner Effect
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="loader-modal"
|
||||||
|
class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-gray-800 p-8 rounded-lg flex flex-col items-center gap-4 border border-gray-700 shadow-xl"
|
||||||
|
>
|
||||||
|
<div class="solid-spinner"></div>
|
||||||
|
<p id="loader-text" class="text-white text-lg font-medium">
|
||||||
|
Processing...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="alert-modal"
|
||||||
|
class="fixed inset-0 bg-gray-900 bg-opacity-90 flex items-center justify-center z-50 hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-gray-800 rounded-lg shadow-xl p-6 max-w-sm w-full border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
id="alert-title"
|
||||||
|
class="text-xl font-bold text-white mb-2"
|
||||||
|
data-i18n="alert.title"
|
||||||
|
>
|
||||||
|
Alert
|
||||||
|
</h3>
|
||||||
|
<p id="alert-message" class="text-gray-300 mb-6"></p>
|
||||||
|
<button
|
||||||
|
id="alert-ok"
|
||||||
|
class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 px-4 rounded-lg transition-colors duration-200"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="max-w-4xl mx-auto px-4 py-12">
|
||||||
|
<h2 class="text-2xl md:text-3xl font-bold text-white mb-8 text-center">
|
||||||
|
How It Works
|
||||||
|
</h2>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
class="flex-shrink-0 w-10 h-10 bg-indigo-600 rounded-full flex items-center justify-center text-white font-bold"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-1">Upload File</h3>
|
||||||
|
<p class="text-gray-400">
|
||||||
|
Click or drag and drop your PDF file to begin
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
class="flex-shrink-0 w-10 h-10 bg-indigo-600 rounded-full flex items-center justify-center text-white font-bold"
|
||||||
|
>
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-1">
|
||||||
|
Adjust Settings
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-400">
|
||||||
|
Customize the scanner effect with real-time preview
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
class="flex-shrink-0 w-10 h-10 bg-indigo-600 rounded-full flex items-center justify-center text-white font-bold"
|
||||||
|
>
|
||||||
|
3
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-1">Download</h3>
|
||||||
|
<p class="text-gray-400">Save your scanned-looking PDF instantly</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="max-w-6xl mx-auto px-4 py-12">
|
||||||
|
<h2 class="text-2xl md:text-3xl font-bold text-white mb-6 text-center">
|
||||||
|
Related PDF Tools
|
||||||
|
</h2>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||||
|
<a
|
||||||
|
href="pdf-to-greyscale.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">PDF to Greyscale</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Free online greyscale tool</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="invert-colors.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">Invert Colors</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Free online invert colors tool</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="compress-pdf.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">Compress PDF</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Free online compress pdf tool</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="rotate-pdf.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">Rotate PDF</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Free online rotate pdf tool</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="edit-pdf.html"
|
||||||
|
class="block bg-gray-800 p-4 rounded-lg hover:bg-gray-700 transition-colors border border-gray-700"
|
||||||
|
>
|
||||||
|
<h3 class="text-white font-semibold mb-1">Edit PDF</h3>
|
||||||
|
<p class="text-gray-400 text-sm">Free online edit pdf tool</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="max-w-4xl mx-auto px-4 py-12">
|
||||||
|
<h2 class="text-2xl md:text-3xl font-bold text-white mb-6 text-center">
|
||||||
|
Frequently Asked Questions
|
||||||
|
</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<details class="bg-gray-800 p-5 rounded-lg border border-gray-700">
|
||||||
|
<summary
|
||||||
|
class="cursor-pointer font-semibold text-white flex items-center justify-between"
|
||||||
|
>
|
||||||
|
Is scanner effect really free?
|
||||||
|
<i data-lucide="chevron-down" class="w-5 h-5"></i>
|
||||||
|
</summary>
|
||||||
|
<p class="mt-3 text-gray-400">
|
||||||
|
Yes! BentoPDF is 100% free with no hidden fees, no signup required,
|
||||||
|
and unlimited file processing.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
<details class="bg-gray-800 p-5 rounded-lg border border-gray-700">
|
||||||
|
<summary
|
||||||
|
class="cursor-pointer font-semibold text-white flex items-center justify-between"
|
||||||
|
>
|
||||||
|
Are my files private and secure?
|
||||||
|
<i data-lucide="chevron-down" class="w-5 h-5"></i>
|
||||||
|
</summary>
|
||||||
|
<p class="mt-3 text-gray-400">
|
||||||
|
Absolutely! All processing happens in your browser. Your files never
|
||||||
|
leave your device, ensuring complete privacy.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
<details class="bg-gray-800 p-5 rounded-lg border border-gray-700">
|
||||||
|
<summary
|
||||||
|
class="cursor-pointer font-semibold text-white flex items-center justify-between"
|
||||||
|
>
|
||||||
|
Is there a file size limit?
|
||||||
|
<i data-lucide="chevron-down" class="w-5 h-5"></i>
|
||||||
|
</summary>
|
||||||
|
<p class="mt-3 text-gray-400">
|
||||||
|
No! Process files of any size, as many times as you want, completely
|
||||||
|
free.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{> footer }}
|
||||||
|
<script type="module" src="/src/js/utils/lucide-init.ts"></script>
|
||||||
|
<script type="module" src="/src/js/utils/full-width.ts"></script>
|
||||||
|
<script type="module" src="/src/js/utils/simple-mode-footer.ts"></script>
|
||||||
|
<script type="module" src="/src/version.ts"></script>
|
||||||
|
<script type="module" src="/src/js/logic/scanner-effect-page.ts"></script>
|
||||||
|
<script type="module" src="/src/js/mobileMenu.ts"></script>
|
||||||
|
<script type="module" src="/src/js/main.ts"></script>
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "SoftwareApplication",
|
||||||
|
"name": "Scanner Effect - BentoPDF",
|
||||||
|
"applicationCategory": "PDF Tool",
|
||||||
|
"operatingSystem": "Any - Web Browser",
|
||||||
|
"offers": {
|
||||||
|
"@type": "Offer",
|
||||||
|
"price": "0",
|
||||||
|
"priceCurrency": "USD"
|
||||||
|
},
|
||||||
|
"aggregateRating": {
|
||||||
|
"@type": "AggregateRating",
|
||||||
|
"ratingValue": "4.7",
|
||||||
|
"ratingCount": "2143"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "HowTo",
|
||||||
|
"name": "How to apply scanner effect online",
|
||||||
|
"description": "Learn how to make a PDF look scanned using BentoPDF",
|
||||||
|
"step": [
|
||||||
|
{
|
||||||
|
"@type": "HowToStep",
|
||||||
|
"position": 1,
|
||||||
|
"name": "Upload File",
|
||||||
|
"text": "Click or drag and drop your PDF file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "HowToStep",
|
||||||
|
"position": 2,
|
||||||
|
"name": "Adjust Settings",
|
||||||
|
"text": "Customize noise, brightness, contrast and more with live preview"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "HowToStep",
|
||||||
|
"position": 3,
|
||||||
|
"name": "Download",
|
||||||
|
"text": "Download your scanned-looking PDF"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "BreadcrumbList",
|
||||||
|
"itemListElement": [
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": 1,
|
||||||
|
"name": "Home",
|
||||||
|
"item": "https://www.bentopdf.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": 2,
|
||||||
|
"name": "Scanner Effect",
|
||||||
|
"item": "https://www.bentopdf.com/scanner-effect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -404,6 +404,8 @@ export default defineConfig(() => {
|
|||||||
'add-watermark': resolve(__dirname, 'src/pages/add-watermark.html'),
|
'add-watermark': resolve(__dirname, 'src/pages/add-watermark.html'),
|
||||||
'header-footer': resolve(__dirname, 'src/pages/header-footer.html'),
|
'header-footer': resolve(__dirname, 'src/pages/header-footer.html'),
|
||||||
'invert-colors': resolve(__dirname, 'src/pages/invert-colors.html'),
|
'invert-colors': resolve(__dirname, 'src/pages/invert-colors.html'),
|
||||||
|
'scanner-effect': resolve(__dirname, 'src/pages/scanner-effect.html'),
|
||||||
|
'adjust-colors': resolve(__dirname, 'src/pages/adjust-colors.html'),
|
||||||
'background-color': resolve(
|
'background-color': resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
'src/pages/background-color.html'
|
'src/pages/background-color.html'
|
||||||
|
|||||||
Reference in New Issue
Block a user