Support raster images as input, --remove-alpha flag to remove alpha channel automatically

This commit is contained in:
Johannes 'josch' Schauer 2020-06-19 16:51:11 +02:00
parent 7b424eb78f
commit ebf32decf2
Signed by: josch
GPG key ID: F2CBA5C78FBD83E1
2 changed files with 122 additions and 29 deletions

View file

@ -6,4 +6,4 @@ Icon=plakativ
Categories=Utility;Graphics Categories=Utility;Graphics
Comment=Create large posters by printing and gluing together smaller pages Comment=Create large posters by printing and gluing together smaller pages
Terminal=false Terminal=false
MimeType=application/pdf;application/epub+zip;application/vnd.ms-xpsdocument; MimeType=application/pdf;application/epub+zip;application/vnd.ms-xpsdocument;image/jpeg;image/png;image/gif;image/tiff;image/bmp;

View file

@ -32,6 +32,7 @@ have_tkinter = True
try: try:
import tkinter import tkinter
import tkinter.filedialog import tkinter.filedialog
import tkinter.messagebox
except ImportError: except ImportError:
have_tkinter = False have_tkinter = False
@ -300,20 +301,8 @@ def complex_cover(n, m, x, y):
class Plakativ: class Plakativ:
def __init__(self, infile, pagenr=0): def __init__(self, doc=None, pagenr=0):
self.doc = None self.doc = doc
if have_img2pdf:
# if we have img2pdf available we can encapsulate a raster image
# into a PDF container
try:
data = img2pdf.convert(infile)
except img2pdf.ImageOpenError:
# img2pdf cannot handle this
pass
else:
stream = BytesIO()
stream.write(data)
self.doc = fitz.open(stream=stream, filetype="application/pdf")
if self.doc is None: if self.doc is None:
# either we didn't have img2pdf or opening the input with img2pdf # either we didn't have img2pdf or opening the input with img2pdf
# failed # failed
@ -818,16 +807,13 @@ class OptionMenu(tkinter.Menubutton):
class Application(tkinter.Frame): class Application(tkinter.Frame):
def __init__(self, master=None, plakativ=None): def __init__(self, master=None):
super().__init__(master) super().__init__(master)
self.master = master self.master = master
self.master.title("plakativ") self.master.title("plakativ")
self.pack(fill=tkinter.BOTH, expand=tkinter.TRUE) self.pack(fill=tkinter.BOTH, expand=tkinter.TRUE)
if plakativ is not None:
self.plakativ = plakativ
self.canvas = tkinter.Canvas(self, bg="black") self.canvas = tkinter.Canvas(self, bg="black")
self.canvas.pack(fill=tkinter.BOTH, side=tkinter.LEFT, expand=tkinter.TRUE) self.canvas.pack(fill=tkinter.BOTH, side=tkinter.LEFT, expand=tkinter.TRUE)
self.canvas_size = self.canvas.winfo_width(), self.canvas.winfo_height() self.canvas_size = self.canvas.winfo_width(), self.canvas.winfo_height()
@ -1109,22 +1095,74 @@ class Application(tkinter.Frame):
# print("saved ", filename) # print("saved ", filename)
def on_open_button(self): def on_open_button(self):
if have_img2pdf:
filetypes = [
("all supported", "*.pdf *.png *.jpg *.jpeg *.gif *.tiff *.tif"),
("pdf documents", "*.pdf"),
("png images", "*.png"),
("jpg images", "*.jpg *.jpeg"),
("gif images", "*.gif"),
("tiff images", "*.tiff *.tif"),
("all files", "*"),
]
else:
filetypes = [
("pdf documents", "*.pdf"),
("all files", "*"),
]
filename = tkinter.filedialog.askopenfilename( filename = tkinter.filedialog.askopenfilename(
parent=self.master, parent=self.master,
title="Open either a PDF or a raster image", title="Open either a PDF or a raster image",
filetypes=[ filetypes=filetypes
("pdf documents", "*.pdf"),
("png images", "*.png"),
("jpg images", "*.jpg"),
("all files", "*"),
],
# initialdir="/home/josch/git/plakativ", # initialdir="/home/josch/git/plakativ",
# initialfile="test.pdf", # initialfile="test.pdf",
) )
if filename == (): if filename == ():
return return
self.open_file(filename)
def open_file(self, filename):
self.filename = filename self.filename = filename
self.plakativ = Plakativ(self.filename) doc = None
if have_img2pdf:
# if we have img2pdf available we can encapsulate a raster image
# into a PDF container
data = None
try:
data = img2pdf.convert(self.filename)
except img2pdf.AlphaChannelError:
remove_alpha = tkinter.messagebox.askyesno(
title="Removing Alpha Channel",
message="PDF does not support alpha channels. Should the "
"alpha channel be removed? The resulting PDF might not be "
"lossless anymore.",
)
# remove alpha channel
if remove_alpha:
from PIL import Image
img = Image.open(self.filename).convert("RGBA")
background = Image.new("RGBA", img.size, (255, 255, 255))
img = Image.alpha_composite(background, img)
with BytesIO() as output:
img.convert("RGB").save(output, format="PNG")
output.seek(0)
data = img2pdf.convert(output)
else:
return
except img2pdf.ImageOpenError:
# img2pdf cannot handle this
pass
if data is not None:
stream = BytesIO()
stream.write(data)
doc = fitz.open(stream=stream, filetype="application/pdf")
if doc is None:
# either we didn't have img2pdf or opening the input with img2pdf
# failed
doc = fitz.open(filename=self.filename)
self.plakativ = Plakativ(doc)
# compute the splitting with the current values # compute the splitting with the current values
mode, (custom_size, size), mult, npages = self.postersize.value mode, (custom_size, size), mult, npages = self.postersize.value
_, pagesize = self.pagesize.value _, pagesize = self.pagesize.value
@ -1725,17 +1763,56 @@ def compute_layout(
pagesize=(210, 297), pagesize=(210, 297),
border=(0, 0, 0, 0), border=(0, 0, 0, 0),
strategy="simple", strategy="simple",
remove_alpha=False,
): ):
plakativ = Plakativ(infile, pagenr) doc = None
if have_img2pdf:
# if we have img2pdf available we can encapsulate a raster image
# into a PDF container
data = None
try:
data = img2pdf.convert(infile)
except img2pdf.AlphaChannelError:
if remove_alpha:
# remove alpha channel
from PIL import Image
img = Image.open(infile).convert("RGBA")
background = Image.new("RGBA", img.size, (255, 255, 255))
img = Image.alpha_composite(background, img)
with BytesIO() as output:
img.convert("RGB").save(output, format="PNG")
output.seek(0)
data = img2pdf.convert(output)
else:
raise
except img2pdf.ImageOpenError:
# img2pdf cannot handle this
pass
if data is not None:
stream = BytesIO()
stream.write(data)
doc = fitz.open(stream=stream, filetype="application/pdf")
if doc is None:
# either we didn't have img2pdf or opening the input with img2pdf
# failed
if hasattr(infile, "read"):
doc = fitz.open(stream=infile, filetype="application/pdf")
else:
doc = fitz.open(filename=infile)
plakativ = Plakativ(doc, pagenr)
plakativ.compute_layout(mode, size, mult, npages, pagesize, border, strategy) plakativ.compute_layout(mode, size, mult, npages, pagesize, border, strategy)
plakativ.render(outfile) plakativ.render(outfile)
def gui(): def gui(filename=None):
if not have_tkinter: if not have_tkinter:
raise Exception("the GUI requires tkinter") raise Exception("the GUI requires tkinter")
root = tkinter.Tk() root = tkinter.Tk()
app = Application(master=root) app = Application(master=root)
if filename is not None:
app.open_file(filename)
app.mainloop() app.mainloop()
@ -1848,6 +1925,7 @@ glued together to form a larger poster. Features:
- GUI with preview functionality - GUI with preview functionality
- complex layouter to save paper - complex layouter to save paper
- optimize by number of pages, output poster size or multiple of input area - optimize by number of pages, output poster size or multiple of input area
- support for raster images as input if img2pdf is available
Options: Options:
""", """,
@ -1906,6 +1984,13 @@ This will create a file poster.pdf with multiple DIN A4 pages which, after
being cut and glued together will form a DIN A1 poster of the content on the being cut and glued together will form a DIN A1 poster of the content on the
first page of input.pdf. first page of input.pdf.
If img2pdf is available as a Python module, then plakativ can also use raster
images as input. Since img2pdf refuses to work on images with an alpha channel,
you can instruct plakativ to remove the alpha channel for you with the
--remove-alpha flag:
$ plakativ --size A1 --output=poster.pdf --remove-alpha input.png
Written by Johannes 'josch' Schauer <josch@mister-muffin.de> Written by Johannes 'josch' Schauer <josch@mister-muffin.de>
Report bugs at https://gitlab.mister-muffin.de/josch/plakativ/issues Report bugs at https://gitlab.mister-muffin.de/josch/plakativ/issues
@ -2013,11 +2098,18 @@ Report bugs at https://gitlab.mister-muffin.de/josch/plakativ/issues
"layout is able to sometimes require less pages for the same poster " "layout is able to sometimes require less pages for the same poster "
"size and is allowed to rotate pages.", "size and is allowed to rotate pages.",
) )
parser.add_argument(
"--remove-alpha",
action="store_true",
help="When the input is a raster image instead of a PDF document, "
"plakativ can remove the alpha channel for you. The resulting PDF "
"poster might not be lossless anymore.",
)
args = parser.parse_args() args = parser.parse_args()
if args.gui: if args.gui:
gui() gui(args.input)
sys.exit(0) sys.exit(0)
if not args.input or args.input == "-": if not args.input or args.input == "-":
@ -2044,6 +2136,7 @@ Report bugs at https://gitlab.mister-muffin.de/josch/plakativ/issues
border=args.border, border=args.border,
strategy=args.layouter, strategy=args.layouter,
**{mode: args.mode}, **{mode: args.mode},
remove_alpha=args.remove_alpha,
) )