Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot add Png image to pdf page #119

Closed
anhtumai opened this issue May 6, 2022 · 16 comments
Closed

Cannot add Png image to pdf page #119

anhtumai opened this issue May 6, 2022 · 16 comments

Comments

@anhtumai
Copy link

anhtumai commented May 6, 2022

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:

use printpdf::*;

use std::fs::File;
use std::io::BufWriter;

fn main() {
    let (doc, page1, layer1) =
        PdfDocument::new("PDF_Document_title", Mm(247.0), Mm(210.0), "Layer 1");
    let current_layer = doc.get_page(page1).get_layer(layer1);

    // currently, the only reliable file formats are bmp/jpeg/png
    // this is an issue of the image library, not a fault of printpdf
    let mut image_file = File::open("inputs/screenshot.png").unwrap();
    let image =
        Image::try_from(image_crate::codecs::png::PngDecoder::new(&mut image_file).unwrap())
            .unwrap();

    println!("{:?}", image.image);

    image.add_to_layer(current_layer.clone(), ImageTransform::default());

    doc.save(&mut BufWriter::new(File::create("output.pdf").unwrap()))
        .unwrap();
}

Cargo.toml:

[package]
name = "test_printpdf"
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", features = ["embedded_images"] }

The PNG file, inputs/screenshot.png:
screenshot

The output PDF, output.pdf:
image

P/s: This problem does not happen when I decode JPEG files with JpegDecoder. Maybe it is only applied for PNG files

@anhtumai
Copy link
Author

anhtumai commented May 6, 2022

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

@fschutt
Copy link
Owner

fschutt commented May 6, 2022

see #84

@anhtumai
Copy link
Author

anhtumai commented May 7, 2022

as I see, the only way to solve this problem to is convert ImageXObject with color_space RGBA to color_space RGB

@anhtumai
Copy link
Author

anhtumai commented May 8, 2022

@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?

@fschutt
Copy link
Owner

fschutt commented May 8, 2022

@anhtumai yeah at least for now it would be a good workaround because this issue comes up again and again

@flying-sheep
Copy link

flying-sheep commented Nov 3, 2022

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)

octagon

renders like this (colorful background for illustration purposes):

octagon

@anhtumai
Copy link
Author

anhtumai commented Nov 3, 2022

@flying-sheep How would you render the image?

I rendered the image with printpdf 0.5.3 and it looks like this:

image

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"] }

@flying-sheep
Copy link

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.

@anhtumai
Copy link
Author

anhtumai commented Nov 4, 2022

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?

@anhtumai
Copy link
Author

@flying-sheep have you solved the problem?

@flying-sheep
Copy link

flying-sheep commented Nov 18, 2022

I looked into it and it’s because in the shape drawing example, global state is modified, sorry.

grafik

(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.

grafik

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();
}

@wjcroom
Copy link

wjcroom commented Dec 3, 2024

截图20241204030742

yes my png is only capture form borowser .
version 0.70

@fschutt
Copy link
Owner

fschutt commented Dec 3, 2024

@wjcroom can you try running https://github.com/fschutt/printpdf/blob/master/examples/image.rs with your image and seeing if that fixes it?

@wjcroom
Copy link

wjcroom commented Dec 4, 2024

@fschutt my printpdf is 0.7 . i dont know how to test it .
my error is lot .
some one is like
error[E0061]: this function takes 4 arguments but 1 argument was supplied --> src/main.rs:26:15 | 26 | let mut doc = PdfDocument::new("My first PDF"); | ^^^^^^^^^^^^^^^^---------------- three arguments are missing |

@fschutt
Copy link
Owner

fschutt commented Dec 7, 2024

@wjcroom test it like in #200 (comment) , reopen if necessary.

Was fixed in master, so this issue can be closed.

@fschutt fschutt closed this as completed Dec 7, 2024
@wjcroom
Copy link

wjcroom commented Dec 9, 2024

remove alpa? if we need alpa, how should we show a png,or bmp ,with alpa message ?I can see all in new pdf form png ,it is a good work ,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants