Open anhtumai opened 2 years ago
One thing to notice is when I try to use img2pdf
(https://github.com/josch/img2pdf) with the PNG image, this is the output:
$> img2pdf inputs/screenshot.png
WARNING:root:Image contains transparency which cannot be retained in PDF.
WARNING:root:img2pdf will not perform a lossy operation.
WARNING:root:You can remove the alpha channel using imagemagick:
WARNING:root: $ convert input.png -background white -alpha remove -alpha off output.png
ERROR:root:error: Refusing to work on images with alpha channel
see #84
as I see, the only way to solve this problem to is convert ImageXObject
with color_space RGBA to color_space RGB
@fschutt I manage to solve the problem by converting ImageXObject with color space Rgba to color space Rgb with white background.
Editted: this only works with Rgba8 or Bgra8 image.
This is the function:
use printpdf::{xobject::ImageXObject, ColorSpace};
pub fn remove_alpha_channel_from_image_x_object(image_x_object: ImageXObject) -> ImageXObject {
if !matches!(image_x_object.color_space, ColorSpace::Rgba)
{
return image_x_object;
};
let ImageXObject {
color_space,
image_data,
..
} = image_x_object;
let new_image_data = image_data
.chunks(4)
.map(|rgba| {
let [red, green, blue, alpha]: [u8; 4] = rgba.try_into().ok().unwrap();
let alpha = alpha as f64 / 255.0;
let new_red = ((1.0 - alpha) * 255.0 + alpha * red as f64) as u8;
let new_green = ((1.0 - alpha) * 255.0 + alpha * green as f64) as u8;
let new_blue = ((1.0 - alpha) * 255.0 + alpha * blue as f64) as u8;
return [new_red, new_green, new_blue];
})
.collect::<Vec<[u8; 3]>>()
.concat();
let new_color_space = match color_space {
ColorSpace::Rgba => ColorSpace::Rgb,
ColorSpace::GreyscaleAlpha => ColorSpace::Greyscale,
other_type => other_type,
};
ImageXObject {
color_space: new_color_space,
image_data: new_image_data,
..image_x_object
}
}
Do you want this to be part of printpdf source code?
@anhtumai yeah at least for now it would be a good workaround because this issue comes up again and again
The workaround doesn’t seem to work for me:
/edit: the workaround works as expected, I just had an interfering layer.set_blend_mode(...)
let mut image = Image::try_from(PngDecoder::new(&mut image_file).unwrap()).unwrap();
image.image = remove_alpha_channel_from_image_x_object(image.image);
this PNG (ColourType=Rgba8)
renders like this (colorful background for illustration purposes):
@flying-sheep How would you render the image?
I rendered the image with printpdf 0.5.3 and it looks like this:
Here is my code
use std::{fs::File, io::BufWriter};
use image_crate::codecs::png::PngDecoder;
use printpdf::{
image_crate, xobject::ImageXObject, ColorSpace, Image, ImageTransform, Mm, PdfDocument,
};
pub fn remove_alpha_channel_from_image_x_object(image_x_object: ImageXObject) -> ImageXObject {
if !matches!(image_x_object.color_space, ColorSpace::Rgba) {
return image_x_object;
};
let ImageXObject {
color_space,
image_data,
..
} = image_x_object;
let new_image_data = image_data
.chunks(4)
.map(|rgba| {
let [red, green, blue, alpha]: [u8; 4] = rgba.try_into().ok().unwrap();
let alpha = alpha as f64 / 255.0;
let new_red = ((1.0 - alpha) * 255.0 + alpha * red as f64) as u8;
let new_green = ((1.0 - alpha) * 255.0 + alpha * green as f64) as u8;
let new_blue = ((1.0 - alpha) * 255.0 + alpha * blue as f64) as u8;
return [new_red, new_green, new_blue];
})
.collect::<Vec<[u8; 3]>>()
.concat();
let new_color_space = match color_space {
ColorSpace::Rgba => ColorSpace::Rgb,
ColorSpace::GreyscaleAlpha => ColorSpace::Greyscale,
other_type => other_type,
};
ImageXObject {
color_space: new_color_space,
image_data: new_image_data,
..image_x_object
}
}
fn main() {
let img_file_name = "/tmp/polygon.png"; // the full path to your polygon image
// open file
let mut image_file = File::open(&img_file_name).unwrap();
let mut image = Image::try_from(PngDecoder::new(&mut image_file).unwrap()).unwrap();
// turn rbga to rgb
image.image = remove_alpha_channel_from_image_x_object(image.image);
// use printpdf to render that image to the new pdf file
let doc = PdfDocument::empty("output-pdf-file");
let (page, layer_index) = doc.add_page(Mm(10.0), Mm(10.0), "ImageLayer");
let current_layer = doc.get_page(page).get_layer(layer_index);
image.add_to_layer(current_layer, ImageTransform::default());
doc.save(&mut BufWriter::new(
File::create("output-pdf-file.pdf").unwrap(),
))
.unwrap();
}
Here is my Cargo.toml content
[package]
name = "test-remove-alpha"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
printpdf = { version = "0.5.3", features = ["embedded_images"] }
you’re going to have to render it in front of anything that isn’t white background to see if it renders correctly or not.
you’re going to have to render it in front of anything that isn’t white background to see if it renders correctly or not.
Can you paste your code here?
@flying-sheep have you solved the problem?
I looked into it and it’s because in the shape drawing example, global state is modified, sorry.
(bit hard to see but it’s all transparent)
Adding a layer.set_blend_mode(BlendMode::Seperable(SeperableBlendMode::Normal));
to the last line of draw_bg
gives it the expected pre-alpha-channel IE6 webpage look.
PS: I think there should be some things implementing Default
, e.g. BlendMode
and SeparableBlendMode
.
use std::{fs::File, io::BufWriter};
use image_crate::codecs::png::PngDecoder;
use printpdf::{
image_crate, xobject::ImageXObject, ColorSpace, Image, ImageTransform, Mm, PdfDocument, Line, Point, PdfLayerReference, Color, Cmyk, Rgb, LineDashPattern, BlendMode, LineCapStyle, LineJoinStyle, SeperableBlendMode, Greyscale,
};
const OCTAGON_BYTES: &'static [u8] = include_bytes!("octagon.png");
pub fn remove_alpha_channel_from_image_x_object(image_x_object: ImageXObject) -> ImageXObject {
if !matches!(image_x_object.color_space, ColorSpace::Rgba) {
return image_x_object;
};
let ImageXObject {
color_space,
image_data,
..
} = image_x_object;
let new_image_data = image_data
.chunks(4)
.map(|rgba| {
let [red, green, blue, alpha]: [u8; 4] = rgba.try_into().ok().unwrap();
let alpha = alpha as f64 / 255.0;
let new_red = ((1.0 - alpha) * 255.0 + alpha * red as f64) as u8;
let new_green = ((1.0 - alpha) * 255.0 + alpha * green as f64) as u8;
let new_blue = ((1.0 - alpha) * 255.0 + alpha * blue as f64) as u8;
return [new_red, new_green, new_blue];
})
.collect::<Vec<[u8; 3]>>()
.concat();
let new_color_space = match color_space {
ColorSpace::Rgba => ColorSpace::Rgb,
ColorSpace::GreyscaleAlpha => ColorSpace::Greyscale,
other_type => other_type,
};
ImageXObject {
color_space: new_color_space,
image_data: new_image_data,
..image_x_object
}
}
fn draw_bg(layer: PdfLayerReference) {
let points1 = vec![
(Point::new(Mm(4.0), Mm(4.0)), false),
(Point::new(Mm(4.0), Mm(7.0)), false),
(Point::new(Mm(7.0), Mm(7.0)), false),
(Point::new(Mm(7.0), Mm(4.0)), false),
];
let line1 = Line {
points: points1,
is_closed: true,
has_fill: true,
has_stroke: true,
is_clipping_path: false,
};
let fill_color_2 = Color::Cmyk(Cmyk::new(0.0, 0.0, 0.0, 0.0, None));
let outline_color_2 = Color::Greyscale(Greyscale::new(0.45, None));
// More advanced graphical options
layer.set_overprint_stroke(true);
layer.set_blend_mode(BlendMode::Seperable(SeperableBlendMode::Multiply));
layer.set_line_cap_style(LineCapStyle::Round);
layer.set_line_join_style(LineJoinStyle::Round);
layer.set_fill_color(fill_color_2);
layer.set_outline_color(outline_color_2);
layer.set_outline_thickness(15.0);
layer.add_shape(line1);
// layer.set_blend_mode(BlendMode::Seperable(SeperableBlendMode::Normal));
}
fn main() {
// open file
let mut image = Image::try_from(PngDecoder::new(OCTAGON_BYTES).unwrap()).unwrap();
// turn rbga to rgb
image.image = remove_alpha_channel_from_image_x_object(image.image);
// use printpdf to render that image to the new pdf file
let doc = PdfDocument::empty("output-pdf-file");
let (page, layer_index) = doc.add_page(Mm(10.0), Mm(10.0), "ImageLayer");
let current_layer = doc.get_page(page).get_layer(layer_index);
draw_bg(current_layer.clone());
image.add_to_layer(current_layer.clone(), ImageTransform::default());
doc.save(&mut BufWriter::new(
File::create("output-pdf-file.pdf").unwrap(),
))
.unwrap();
}
Every time I try to decode a PNG file with PngDecoded and add it to the PDF page, the PDF page is blank.
My Rust code:
Cargo.toml:
The PNG file,
inputs/screenshot.png
:The output PDF,
output.pdf
:P/s: This problem does not happen when I decode JPEG files with JpegDecoder. Maybe it is only applied for PNG files