Export alpha channel into PDF 🧊

This commit is contained in:
Laurenz 2020-11-28 00:04:40 +01:00
parent 982e7671a6
commit 21857064db
7 changed files with 65 additions and 25 deletions

View File

@ -5,7 +5,7 @@ use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
use fontdock::FaceId; use fontdock::FaceId;
use image::{DynamicImage, GenericImageView}; use image::{DynamicImage, GenericImageView, Rgba};
use pdf_writer::{ use pdf_writer::{
CidFontType, ColorSpace, Content, FontFlags, Name, PdfWriter, Rect, Ref, Str, CidFontType, ColorSpace, Content, FontFlags, Name, PdfWriter, Rect, Ref, Str,
SystemInfo, UnicodeCmap, SystemInfo, UnicodeCmap,
@ -43,17 +43,24 @@ impl<'a> PdfExporter<'a> {
let mut fonts = Remapper::new(); let mut fonts = Remapper::new();
let mut images = Remapper::new(); let mut images = Remapper::new();
let mut alpha_masks = 0;
for layout in layouts { for layout in layouts {
for (_, element) in &layout.elements { for (_, element) in &layout.elements {
match element { match element {
LayoutElement::Text(shaped) => fonts.insert(shaped.face), LayoutElement::Text(shaped) => fonts.insert(shaped.face),
LayoutElement::Image(image) => images.insert(image.resource), LayoutElement::Image(image) => {
let buf = env.resources.get_loaded::<DynamicImage>(image.res);
if buf.color().has_alpha() {
alpha_masks += 1;
}
images.insert(image.res);
}
} }
} }
} }
let refs = Refs::new(layouts.len(), fonts.len(), images.len()); let refs = Refs::new(layouts.len(), fonts.len(), images.len(), alpha_masks);
Self { Self {
writer, writer,
@ -154,7 +161,7 @@ impl<'a> PdfExporter<'a> {
for (pos, element) in &page.elements { for (pos, element) in &page.elements {
if let LayoutElement::Image(image) = element { if let LayoutElement::Image(image) = element {
let name = format!("Im{}", self.images.map(image.resource)); let name = format!("Im{}", self.images.map(image.res));
let size = image.size; let size = image.size;
let x = pos.x.to_pt() as f32; let x = pos.x.to_pt() as f32;
let y = (page.size.height - pos.y - size.height).to_pt() as f32; let y = (page.size.height - pos.y - size.height).to_pt() as f32;
@ -281,15 +288,40 @@ impl<'a> PdfExporter<'a> {
} }
fn write_images(&mut self) { fn write_images(&mut self) {
let mut mask = 0;
for (id, resource) in self.refs.images().zip(self.images.layout_indices()) { for (id, resource) in self.refs.images().zip(self.images.layout_indices()) {
let image = self.env.resources.get_loaded::<DynamicImage>(resource); let buf = self.env.resources.get_loaded::<DynamicImage>(resource);
let data = image.to_rgb8().into_raw(); let data = buf.to_rgb8().into_raw();
self.writer
.image_stream(id, &data) let mut image = self.writer.image_stream(id, &data);
.width(image.width() as i32) image.width(buf.width() as i32);
.height(image.height() as i32) image.height(buf.height() as i32);
.color_space(ColorSpace::DeviceRGB) image.color_space(ColorSpace::DeviceRGB);
.bits_per_component(8); image.bits_per_component(8);
// Add a second gray-scale image containing the alpha values if this
// is image has an alpha channel.
if buf.color().has_alpha() {
let mask_id = self.refs.alpha_mask(mask);
image.s_mask(mask_id);
drop(image);
let mut samples = vec![];
for (_, _, Rgba([_, _, _, a])) in buf.pixels() {
samples.push(a);
}
self.writer
.image_stream(mask_id, &samples)
.width(buf.width() as i32)
.height(buf.height() as i32)
.color_space(ColorSpace::DeviceGray)
.bits_per_component(8);
mask += 1;
}
} }
} }
} }
@ -304,6 +336,7 @@ struct Refs {
contents_start: i32, contents_start: i32,
fonts_start: i32, fonts_start: i32,
images_start: i32, images_start: i32,
alpha_masks_start: i32,
end: i32, end: i32,
} }
@ -318,14 +351,15 @@ struct FontRefs {
impl Refs { impl Refs {
const OBJECTS_PER_FONT: usize = 5; const OBJECTS_PER_FONT: usize = 5;
fn new(layouts: usize, fonts: usize, images: usize) -> Self { fn new(layouts: usize, fonts: usize, images: usize, alpha_masks: usize) -> Self {
let catalog = 1; let catalog = 1;
let page_tree = catalog + 1; let page_tree = catalog + 1;
let pages_start = page_tree + 1; let pages_start = page_tree + 1;
let contents_start = pages_start + layouts as i32; let contents_start = pages_start + layouts as i32;
let fonts_start = contents_start + layouts as i32; let fonts_start = contents_start + layouts as i32;
let images_start = fonts_start + (Self::OBJECTS_PER_FONT * fonts) as i32; let images_start = fonts_start + (Self::OBJECTS_PER_FONT * fonts) as i32;
let end = images_start + images as i32; let alpha_masks_start = images_start + images as i32;
let end = alpha_masks_start + alpha_masks as i32;
Self { Self {
catalog: Ref::new(catalog), catalog: Ref::new(catalog),
@ -334,6 +368,7 @@ impl Refs {
contents_start, contents_start,
fonts_start, fonts_start,
images_start, images_start,
alpha_masks_start,
end, end,
} }
} }
@ -361,6 +396,10 @@ impl Refs {
fn images(&self) -> impl Iterator<Item = Ref> { fn images(&self) -> impl Iterator<Item = Ref> {
(self.images_start .. self.end).map(Ref::new) (self.images_start .. self.end).map(Ref::new)
} }
fn alpha_mask(&self, i: usize) -> Ref {
Ref::new(self.alpha_masks_start + i as i32)
}
} }
/// Used to assign new, consecutive PDF-internal indices to things. /// Used to assign new, consecutive PDF-internal indices to things.

View File

@ -183,7 +183,7 @@ pub enum LayoutElement {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ImageElement { pub struct ImageElement {
/// The image. /// The image.
pub resource: ResourceId, pub res: ResourceId,
/// The document size of the image. /// The document size of the image.
pub size: Size, pub size: Size,
} }

View File

@ -27,11 +27,11 @@ pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
.and_then(|reader| reader.decode().ok()) .and_then(|reader| reader.decode().ok())
}); });
if let Some((resource, buf)) = loaded { if let Some((res, buf)) = loaded {
let dimensions = buf.dimensions(); let dimensions = buf.dimensions();
drop(env); drop(env);
ctx.push(Image { ctx.push(Image {
resource, res,
dimensions, dimensions,
width, width,
height, height,
@ -50,7 +50,7 @@ pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
struct Image { struct Image {
/// The resource id of the image file. /// The resource id of the image file.
resource: ResourceId, res: ResourceId,
/// The pixel dimensions of the image. /// The pixel dimensions of the image.
dimensions: (u32, u32), dimensions: (u32, u32),
/// The fixed width, if any. /// The fixed width, if any.
@ -87,7 +87,7 @@ impl Layout for Image {
let mut boxed = BoxLayout::new(size); let mut boxed = BoxLayout::new(size);
boxed.push( boxed.push(
Point::ZERO, Point::ZERO,
LayoutElement::Image(ImageElement { resource: self.resource, size }), LayoutElement::Image(ImageElement { res: self.res, size }),
); );
Layouted::Layout(boxed, self.align) Layouted::Layout(boxed, self.align)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 KiB

After

Width:  |  Height:  |  Size: 292 KiB

BIN
tests/res/tiger-alpha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

@ -1,14 +1,15 @@
[page: width=10cm, height=10cm, margins=1cm] [page: width=5cm, height=5cm, margins=0.25cm]
[image: "res/tiger.jpg"] [image: "res/tiger.jpg"]
[pagebreak] [pagebreak]
# Tiger # Tiger
[image: "res/tiger.jpg", width=3cm] [image: "res/tiger.jpg", width=2cm]
[image: "res/tiger.jpg", height=3cm] [image: "res/tiger-alpha.png", width=1cm]
[image: "res/tiger-alpha.png", height=2cm]
[pagebreak] [pagebreak]
[align: center] [align: center, bottom]
[image: "res/tiger.jpg", width=6cm, height=6cm] [image: "res/tiger.jpg", width=2cm, height=3.5cm]

View File

@ -248,7 +248,7 @@ fn draw_text(canvas: &mut Canvas, pos: Point, env: &Env, shaped: &Shaped) {
} }
fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, image: &ImageElement) { fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, image: &ImageElement) {
let buf = env.resources.get_loaded::<DynamicImage>(image.resource); let buf = env.resources.get_loaded::<DynamicImage>(image.res);
let mut pixmap = Pixmap::new(buf.width(), buf.height()).unwrap(); let mut pixmap = Pixmap::new(buf.width(), buf.height()).unwrap();
for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) { for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) {