Skip to content

Commit 42f9f4d

Browse files
feat(experimental): pdf background removal (#233)
1 parent 56bdfe6 commit 42f9f4d

35 files changed

Lines changed: 882 additions & 245 deletions

cmd/pdfbgremove/main.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"sort"
8+
9+
"github.com/pdfcpu/pdfcpu/pkg/api"
10+
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
11+
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
12+
"github.com/rmitchellscott/aviary/internal/security"
13+
)
14+
15+
type imageInfo struct {
16+
ObjNr int
17+
Width int
18+
Height int
19+
Area int
20+
PageNr int
21+
}
22+
23+
func main() {
24+
if len(os.Args) < 2 {
25+
fmt.Println("Usage: pdfbgremove <input.pdf> [output.pdf]")
26+
os.Exit(1)
27+
}
28+
29+
inputPath := os.Args[1]
30+
outputPath := inputPath[:len(inputPath)-4] + "_cleaned.pdf"
31+
if len(os.Args) >= 3 {
32+
outputPath = os.Args[2]
33+
}
34+
35+
removed, err := removeBackgroundImages(inputPath, outputPath)
36+
if err != nil {
37+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
38+
os.Exit(1)
39+
}
40+
41+
fmt.Printf("Removed %d background image(s)\n", removed)
42+
fmt.Printf("Output saved to: %s\n", outputPath)
43+
}
44+
45+
func removeBackgroundImages(inputPath, outputPath string) (int, error) {
46+
secureInput, err := security.NewSecurePathFromExisting(inputPath)
47+
if err != nil {
48+
return 0, fmt.Errorf("invalid input path: %w", err)
49+
}
50+
secureOutput, err := security.NewSecurePathFromExisting(outputPath)
51+
if err != nil {
52+
return 0, fmt.Errorf("invalid output path: %w", err)
53+
}
54+
55+
inFile, err := security.SafeOpen(secureInput)
56+
if err != nil {
57+
return 0, fmt.Errorf("failed to open input PDF: %w", err)
58+
}
59+
defer inFile.Close()
60+
61+
conf := model.NewDefaultConfiguration()
62+
ctx, err := api.ReadContext(inFile, conf)
63+
if err != nil {
64+
return 0, fmt.Errorf("failed to read PDF context: %w", err)
65+
}
66+
67+
if err := api.OptimizeContext(ctx); err != nil {
68+
return 0, fmt.Errorf("failed to optimize PDF context: %w", err)
69+
}
70+
71+
if err := ctx.EnsurePageCount(); err != nil {
72+
return 0, fmt.Errorf("failed to ensure page count: %w", err)
73+
}
74+
75+
inFile.Seek(0, io.SeekStart)
76+
allImages, err := api.Images(inFile, nil, conf)
77+
if err != nil {
78+
return 0, fmt.Errorf("failed to get images from PDF: %w", err)
79+
}
80+
81+
pageCount := len(allImages)
82+
fmt.Printf("Processing %d pages...\n", pageCount)
83+
84+
removedCount := 0
85+
86+
for pageNum := 1; pageNum <= pageCount; pageNum++ {
87+
var pageImages []imageInfo
88+
for _, pageMap := range allImages {
89+
for objNr, img := range pageMap {
90+
if img.PageNr == pageNum {
91+
pageImages = append(pageImages, imageInfo{
92+
ObjNr: objNr,
93+
Width: img.Width,
94+
Height: img.Height,
95+
Area: img.Width * img.Height,
96+
PageNr: pageNum,
97+
})
98+
}
99+
}
100+
}
101+
102+
if len(pageImages) < 2 {
103+
continue
104+
}
105+
106+
sort.Slice(pageImages, func(i, j int) bool {
107+
return pageImages[i].Area < pageImages[j].Area
108+
})
109+
110+
smallest := pageImages[0]
111+
fmt.Printf("Page %d: removing background (%dx%d)\n", pageNum, smallest.Width, smallest.Height)
112+
113+
if err := removeImageFromPage(ctx, pageNum, smallest.ObjNr); err != nil {
114+
fmt.Printf(" Warning: %v\n", err)
115+
continue
116+
}
117+
removedCount++
118+
}
119+
120+
if removedCount == 0 {
121+
fmt.Println("No background images to remove, copying file as-is")
122+
inFile.Seek(0, io.SeekStart)
123+
outFile, err := security.SafeCreate(secureOutput)
124+
if err != nil {
125+
return 0, fmt.Errorf("failed to create output file: %w", err)
126+
}
127+
defer outFile.Close()
128+
if _, err := io.Copy(outFile, inFile); err != nil {
129+
return 0, fmt.Errorf("failed to copy file: %w", err)
130+
}
131+
return 0, nil
132+
}
133+
134+
outFile, err := security.SafeCreate(secureOutput)
135+
if err != nil {
136+
return 0, fmt.Errorf("failed to create output file: %w", err)
137+
}
138+
defer outFile.Close()
139+
140+
if err := api.WriteContext(ctx, outFile); err != nil {
141+
return 0, fmt.Errorf("failed to write modified PDF: %w", err)
142+
}
143+
144+
return removedCount, nil
145+
}
146+
147+
func removeImageFromPage(ctx *model.Context, pageNr int, objNr int) error {
148+
pageDict, _, inheritedAttrs, err := ctx.PageDict(pageNr, true)
149+
if err != nil {
150+
return fmt.Errorf("failed to get page dict: %w", err)
151+
}
152+
153+
var resDict types.Dict
154+
if inheritedAttrs != nil && inheritedAttrs.Resources != nil {
155+
resDict = inheritedAttrs.Resources
156+
} else {
157+
resDict = pageDict.DictEntry("Resources")
158+
}
159+
160+
if resDict == nil {
161+
return fmt.Errorf("page %d has no Resources dictionary", pageNr)
162+
}
163+
164+
xobjEntry, found := resDict.Find("XObject")
165+
if !found {
166+
return fmt.Errorf("page %d Resources has no XObject entry", pageNr)
167+
}
168+
169+
var xobjDict types.Dict
170+
switch v := xobjEntry.(type) {
171+
case types.Dict:
172+
xobjDict = v
173+
case types.IndirectRef:
174+
deref, err := ctx.Dereference(v)
175+
if err != nil {
176+
return fmt.Errorf("failed to dereference XObject dict: %w", err)
177+
}
178+
var ok bool
179+
xobjDict, ok = deref.(types.Dict)
180+
if !ok {
181+
return fmt.Errorf("XObject is not a dictionary")
182+
}
183+
default:
184+
return fmt.Errorf("unexpected XObject type: %T", xobjEntry)
185+
}
186+
187+
var keyToRemove string
188+
for key, val := range xobjDict {
189+
if indRef, ok := val.(types.IndirectRef); ok {
190+
if int(indRef.ObjectNumber) == objNr {
191+
keyToRemove = key
192+
break
193+
}
194+
}
195+
}
196+
197+
if keyToRemove == "" {
198+
return fmt.Errorf("could not find XObject key for objNr %d", objNr)
199+
}
200+
201+
xobjDict.Delete(keyToRemove)
202+
ctx.FreeObject(objNr)
203+
204+
return nil
205+
}

docs/API.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Upload documents by providing a URL to download from.
2222
| conflict_resolution | No | abort/overwrite/content_only | Override conflict resolution when file exists. Defaults to user/environment setting. |
2323
| coverpage | No | current/first | Override coverpage setting for PDF uploads. Defaults to user/environment setting. |
2424
| outputFormat | No | pdf/epub | Output format for web articles, HTML, and Markdown. Defaults to CONVERSION_OUTPUT_FORMAT or epub. |
25+
| remove_background | No | true/false | Remove background images from PDF (experimental). Defaults to user setting if enabled. |
2526

2627
### Document content uploads (JSON)
2728

@@ -42,6 +43,7 @@ Upload documents by providing base64-encoded content directly.
4243
| conflict_resolution | No | abort/overwrite/content_only | Override conflict resolution when file exists |
4344
| coverpage | No | current/first | Override coverpage setting for PDF uploads |
4445
| outputFormat | No | pdf/epub | Output format for HTML and Markdown files. Defaults to CONVERSION_OUTPUT_FORMAT or epub. |
46+
| removeBackground | No | true/false | Remove background images from PDF (experimental). Defaults to user setting if enabled. |
4547

4648
**Supported content types:** PDF, JPEG, PNG, EPUB, Markdown (.md), HTML (.html)
4749

docs/CONFIGURATION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ This is particularly useful when using Docker secrets, Kubernetes secrets, or ot
4646
| PAGE_RESOLUTION | No | 1404x1872 | Page resolution for PDF conversion (WIDTHxHEIGHT format), used as the default in multi-user mode |
4747
| PAGE_DPI | No | 226 | Page DPI for PDF conversion, used as the default in multi-user mode |
4848
| CONVERSION_OUTPUT_FORMAT | No | epub | Default output format for web articles, HTML, and Markdown conversion (`pdf` or `epub`) |
49+
| PDF_BACKGROUND_REMOVAL | No | false | Remove background images from scanned PDFs (experimental) |
4950
| DRY_RUN | No | false | Set to `true` to log rmapi commands without running them |
5051
| MAX_UPLOAD_SIZE | No | 524288000 | Maximum file upload size in bytes (default: 500MB) |
5152

go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/golang-jwt/jwt/v5 v5.3.0
2323
github.com/google/uuid v1.6.0
2424
github.com/joho/godotenv v1.5.1
25+
github.com/pdfcpu/pdfcpu v0.11.1
2526
github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50
2627
github.com/yuin/goldmark v1.7.13
2728
golang.org/x/crypto v0.45.0
@@ -52,6 +53,7 @@ require (
5253
github.com/aws/smithy-go v1.23.0 // indirect
5354
github.com/bytedance/sonic v1.14.0 // indirect
5455
github.com/bytedance/sonic/loader v0.3.0 // indirect
56+
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
5557
github.com/cloudwego/base64x v0.1.6 // indirect
5658
github.com/dustin/go-humanize v1.0.1 // indirect
5759
github.com/gin-contrib/sse v1.1.0 // indirect
@@ -63,6 +65,9 @@ require (
6365
github.com/goccy/go-json v0.10.2 // indirect
6466
github.com/gofrs/uuid v3.1.0+incompatible // indirect
6567
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
68+
github.com/hhrutter/lzw v1.0.0 // indirect
69+
github.com/hhrutter/pkcs7 v0.2.0 // indirect
70+
github.com/hhrutter/tiff v1.0.2 // indirect
6671
github.com/jackc/pgpassfile v1.0.0 // indirect
6772
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
6873
github.com/jackc/pgx/v5 v5.6.0 // indirect
@@ -74,20 +79,24 @@ require (
7479
github.com/kr/text v0.2.0 // indirect
7580
github.com/leodido/go-urn v1.4.0 // indirect
7681
github.com/mattn/go-isatty v0.0.20 // indirect
82+
github.com/mattn/go-runewidth v0.0.19 // indirect
7783
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
7884
github.com/modern-go/reflect2 v1.0.2 // indirect
7985
github.com/ncruces/go-strftime v0.1.9 // indirect
8086
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
87+
github.com/pkg/errors v0.9.1 // indirect
8188
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
8289
github.com/rogpeppe/go-internal v1.14.1 // indirect
8390
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
8491
github.com/ugorji/go/codec v1.3.0 // indirect
8592
golang.org/x/arch v0.20.0 // indirect
8693
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
94+
golang.org/x/image v0.32.0 // indirect
8795
golang.org/x/net v0.47.0 // indirect
8896
golang.org/x/sync v0.18.0 // indirect
8997
golang.org/x/sys v0.38.0 // indirect
9098
google.golang.org/protobuf v1.36.9 // indirect
99+
gopkg.in/yaml.v2 v2.4.0 // indirect
91100
gopkg.in/yaml.v3 v3.0.1 // indirect
92101
modernc.org/libc v1.66.4 // indirect
93102
modernc.org/mathutil v1.7.1 // indirect

go.sum

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQ
4848
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
4949
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
5050
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
51+
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
52+
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
5153
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
5254
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
5355
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
@@ -101,6 +103,12 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17k
101103
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
102104
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
103105
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
106+
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
107+
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
108+
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
109+
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
110+
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
111+
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
104112
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
105113
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
106114
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -128,15 +136,21 @@ github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjS
128136
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
129137
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
130138
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
139+
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
140+
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
131141
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
132142
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
133143
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
134144
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
135145
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
136146
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
137147
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
148+
github.com/pdfcpu/pdfcpu v0.11.1 h1:htHBSkGH5jMKWC6e0sihBFbcKZ8vG1M67c8/dJxhjas=
149+
github.com/pdfcpu/pdfcpu v0.11.1/go.mod h1:pP3aGga7pRvwFWAm9WwFvo+V68DfANi9kxSQYioNYcw=
138150
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
139151
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
152+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
153+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
140154
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
141155
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
142156
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -178,6 +192,8 @@ golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
178192
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
179193
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
180194
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
195+
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
196+
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
181197
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
182198
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
183199
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -262,6 +278,8 @@ google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXn
262278
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
263279
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
264280
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
281+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
282+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
265283
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
266284
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
267285
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)