生成离线报告的一种方式
用puppeteer(node服务)打开网页生成图片,然后将图片替换到PPT的模板中。
有一个问题是生成的图片会有很多空白部分,所以要用java将白色背景透明化, 一种方式是将图片的白色背景透明,另一种方式是将图片的白色背景部分剪裁掉。PPT模板图片事先根据固定尺寸大小建好的,如果替换的图片大小不一致就可能导致图片拉伸变形
最终采用生成离线报告的方案
- 创建图片目录
- puppeteer打开网页生成图片
- (可选)java程序用poi插件不用模板直接将图片输出到PPT中
- (可选)制作一个PPT模板,将第3步生成PPT中的图片拷贝过来,保持纵横比调整图片,底部白色部分会超过PPT页的大小,只需要保证有内容的部分都在PPT页中就可以了,注意PPT模板中相似的图片不要直接复制要使用不同的图片
- 将puppeteer生成的图片的白色背景变为透明
- 将处理过的图片替换到模板PPT的图片中
puppeteer生成图片的关键代码:
const puppeteer = require('puppeteer');
const request = require('request');
async function createImage(url, imagePath) {
console.log(`==================== 开始生成图片:${imagePath} =========================`);
console.time('生成图片花费时间');
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
const page = await browser.newPage();
page.setViewport({ width: 1200, height: 800});
try {
await page.goto(url);
await Promise.race([
page.waitForNavigation({ waitUntil: "networkidle0", timeout: 0 }),
// 等待页面元素渲染完成 我们的网页渲染完以后会添加一个chart-download的class属性,这个根据情况定制
page.waitForSelector('.chart-download')
])
await page.screenshot({path: imagePath, fullPage: true});
} catch (error) {
console.log('Error: ', error);
throw error;
} finally {
await browser.close();
console.timeEnd('生成图片花费时间');
console.log('当前日期:', (new Date()).toLocaleString());
console.log(`==================== 结束生成图片:${imagePath} =========================`);
}
}
module.exports = createImage;
java将图片白色背景透明化:
原文java对图片进行透明化处理
原代码对图片处理过一次以后处理第二次就会出现黑色背景,做了一点点改动
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
@Slf4j
public class PictureUtil {
public static void removeWhiteBackground(String path) {
try {
BufferedImage image = ImageIO.read(new File(path));
ImageIcon imageIcon = new ImageIcon(image);
BufferedImage bufferedImage = new BufferedImage(
imageIcon.getIconWidth(), imageIcon.getIconHeight(),
BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2D = (Graphics2D) bufferedImage.getGraphics();
g2D.drawImage(imageIcon.getImage(), 0, 0,
imageIcon.getImageObserver());
int alpha = 0;
for (int j1 = bufferedImage.getMinY(); j1 < bufferedImage
.getHeight(); j1++) {
for (int j2 = bufferedImage.getMinX(); j2 < bufferedImage
.getWidth(); j2++) {
int rgb = bufferedImage.getRGB(j2, j1);
Color color = new Color(rgb, true);
// 如果已经透明了就不再处理
if(color.getAlpha() == 0){
continue;
}
if (colorInRange(rgb))
alpha = 0;
else
alpha = 255;
rgb = (alpha << 24) | (rgb & 0x00ffffff);
bufferedImage.setRGB(j2, j1, rgb);
}
}
g2D.drawImage(bufferedImage, 0, 0, imageIcon.getImageObserver());
// 生成图片为PNG
String outFile = path.substring(0, path.lastIndexOf("."));
ImageIO.write(bufferedImage, "png", new File(outFile + ".png"));
} catch (IOException e) {
log.error(ExceptionUtils.getFullStackTrace(e));
}
}
public static boolean colorInRange(int color) {
int red = (color & 0xff0000) >> 16;
int green = (color & 0x00ff00) >> 8;
int blue = (color & 0x0000ff);
if (red >= color_range && green >= color_range && blue >= color_range)
return true;
return false;
}
// 根据需要自己调整这个阈值,如果只需要去掉纯白色背景就设为255
public static int color_range = 250;
}
另外,把java去除图片多余白色背景部分的代码粘贴一下,原作者博客地址不记得了
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
* Created by liufang on 2019/4/10.
*/
public class PictureTrimWhite {
private BufferedImage img;
// 图片右边和底部还需要保留一点白色背景,否则太难看了
private static final int REMAIN_WIDTH = 20;
public PictureTrimWhite(File input) {
try {
img = ImageIO.read(input);
} catch (IOException e) {
throw new RuntimeException( "Problem reading image", e );
}
}
public void trim() {
int width = getTrimmedWidth();
int height = getTrimmedHeight();
BufferedImage newImg = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics g = newImg.createGraphics();
g.drawImage( img, 0, 0, null );
img = newImg;
}
public void write(File f) {
try {
ImageIO.write(img, "png", f);
} catch (IOException e) {
throw new RuntimeException( "Problem writing image", e );
}
}
private int getTrimmedWidth() {
int height = this.img.getHeight();
int width = this.img.getWidth();
int trimmedWidth = 0;
for(int i = 0; i < height; i++) {
for(int j = width - 1; j >= 0; j--) {
if(img.getRGB(j, i) != Color.WHITE.getRGB() &&
j > trimmedWidth) {
trimmedWidth = j;
break;
}
}
}
trimmedWidth += REMAIN_WIDTH;
if(trimmedWidth>width){
trimmedWidth = width;
}
return trimmedWidth;
}
private int getTrimmedHeight() {
int width = this.img.getWidth();
int height = this.img.getHeight();
int trimmedHeight = 0;
for(int i = 0; i < width; i++) {
for(int j = height - 1; j >= 0; j--) {
if(img.getRGB(i, j) != Color.WHITE.getRGB() &&
j > trimmedHeight) {
trimmedHeight = j;
break;
}
}
}
trimmedHeight += REMAIN_WIDTH;
if(trimmedHeight>height){
trimmedHeight = height;
}
return trimmedHeight;
}
public static void main(String[] args) {
PictureTrimWhite trim = new PictureTrimWhite(new File("C:\\Users\\Administrator\\Desktop\\tmp\\离线图片.png"));
trim.trim();
trim.write(new File("E:\\tmp\\tmp.png"));
}
}
poi将图片替换到PPT模板部分代码也在这里粘贴一下
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.xslf.usermodel.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
@Slf4j
public class PPTUtil {
// 不带模板生成PPT
public static boolean writePptFile(File pptFile, List<String> picPaths) {
if (!pptFile.exists()) {
pptFile.getParentFile().mkdirs();
}
try(FileOutputStream out = new FileOutputStream(pptFile)){
if (picPaths != null && picPaths.size() > 0) {
// 构建PPT
XMLSlideShow ppt = new XMLSlideShow();
for (String picPath : picPaths) {
// 创建幻灯片
XSLFSlide blankSlide = ppt.createSlide();
XSLFPictureData pd = ppt.addPicture(org.apache.commons.io.FileUtils.readFileToByteArray(new File(picPath)), PictureData.PictureType.PNG);
blankSlide.createPicture(pd);
}
// 输出PPT文件
ppt.write(out);
return true;
}
}catch (IOException e){
log.error(ExceptionUtils.getFullStackTrace(e));
}
return false;
}
// 根据PPT模板生成PPT
public static boolean writePptFile(File pptFile, String motherPpt, String reportDate, List<OnlineReportImage> reports) {
if (!pptFile.exists()) {
pptFile.getParentFile().mkdirs();
}
try(FileOutputStream out = new FileOutputStream(pptFile);FileInputStream mother = new FileInputStream(motherPpt)){
if (reports != null && reports.size() > 0) {
// 从模板页中构建PPT
XMLSlideShow ppt = new XMLSlideShow(mother);
// 第一页模板页替换内容
XSLFSlide slide1 = ppt.getSlides().get(0);
for (XSLFShape shape : slide1.getShapes()) {
if (shape instanceof XSLFTextShape) {
XSLFTextShape txShape = (XSLFTextShape) shape;
if (txShape.getText().contains("{reportDate}")) {
// 替换文字内容
txShape.setText(txShape.getText().replace("{reportDate}", reportDate));
}
}
}
for (OnlineReportImage report : reports) {
XSLFSlide slide = ppt.getSlides().get(report.getLocation() - 1);
for (XSLFShape shape : slide.getShapes()) {
if (shape instanceof XSLFPictureShape) {
XSLFPictureShape pShape = (XSLFPictureShape) shape;
XSLFPictureData pData = pShape.getPictureData();
pData.setData(org.apache.commons.io.FileUtils.readFileToByteArray(new File(report.getFile())));
}
}
}
// 输出PPT文件
ppt.write(out);
return true;
}
}catch (IOException e){
pptFile.delete();
log.error(ExceptionUtils.getFullStackTrace(e));
}
return false;
}
}
@Data
public class OnlineReportImage {
/**
* 生成的图片路径
*/
private String file;
/**
* 图片在PPT模板中的位置
*/
private int location;
}