实现pdf同步/异步解析图片最佳实践 pdf2img pdfbox2.x 配合异步线程池来解决方案 极致压缩服务性能 坑都已踩过 已上线服务 效率50ms/张 异步多线程环境下报字错误等 附关键源码

pdf2img最佳实践(pdfbox2.x springboot 线程池)

前言

相信大家在做pdf2img中,大多都是从github中寻找的相关的资源代码,其中我采用的apache家的pdfbox的jar包来的,采用的是2.0.16和2.0.24版本的服务,其中有很好的解决方案。本篇文章不会说linux环境下字体库的事,大多99%的情况下把window的宋体等相关资源字体库加进去就会好。我在这里,就是实战中,pdf2img中采用线程池异步解析pdf成图片集的最佳实践在测试linux环境中(48核8g堆大小)一般为50ms左右,也就是说一份20页的pdf能在1s时间解析成20张图片,效率上还是很快很快的

效果

废话不多说,先看效果图,环境linux(48核8g堆大小),37*2=74张图(高清+缩略) 近1
m大小的pdf文件,关键三方jar包(https://pdfbox.apache.org/)
动画演示
效率图
业务上事解析每张为高清和缩略图,要上传到redis中,折算下来50ms每张。

代码

线程池(最佳实践过了)

/**
 * TaskExecutor
 * 任务线程池
 *
 * @author cc
 * @date 2020/06/04
 */
@Slf4j
@Component
public class TaskExecutor {

    /**
     * 装配自定义线城池
     *
     * @return
     */
    @Bean("myTaskExecutor")
    public static Executor myTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数量,线程池创建时候初始化的线程数
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
        // 最大线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize((int) (executor.getCorePoolSize() * 1.5));
        // 缓冲队列,用来缓冲执行任务的队列
        executor.setQueueCapacity(200);
        // 当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        // 设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("myTaskExecutorThread-");
        // 用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
        executor.setAwaitTerminationSeconds(60);
        // 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 线程池初始化加载
        executor.initialize();
        log.info("MyTaskExecutor loaded and initialize");
        log.info("********** MyTaskExecutor info **********");
        log.info("********** CorePoolSize: {} **********", executor.getCorePoolSize());
        log.info("********** MaxPoolSize: {} **********", executor.getMaxPoolSize());
        log.info("********** KeepAliveSeconds: {} **********", executor.getKeepAliveSeconds());


        return executor;
    }

}

异步业务

/**
 * BusinessAsync
 * 业务异步
 *
 * @author cc
 * @date 2020/06/04
 */
@Slf4j
@Component
public class BusinessAsync {

    @Autowired
    @Lazy
    private IBusinessAsyncService businessAsyncService;


    /**
     * 示例
     *
     * @return
     */
    @Async("myTaskExecutor")
    public CompletableFuture<String> example() {
        System.out.println(Thread.currentThread().getName());
        log.info("async example");
        return CompletableFuture.completedFuture("完成了async example");
    }

    @Async("myTaskExecutor")
    public CompletableFuture<String> autoPdfParseToAbbsAndHds(String allQueryId, byte[] bytes, int pages, boolean abbsAndHdsAsyn) throws IOException {
        log.info("Thread : {}-->,正在执行 autoPdfParseToAbbsAndHds", Thread.currentThread().getName());
        log.info("allQueryId : {}", allQueryId);
        boolean autoPdfParseToAbbsAndHdsFlag = businessAsyncService.autoPdfParseToAbbsAndHdProcess(allQueryId, bytes, pages, abbsAndHdsAsyn);
        log.info("autoPdfParseToAbbsAndHdsFlag-->{}", autoPdfParseToAbbsAndHdsFlag);
        return CompletableFuture.completedFuture("完成了async autoPdfParseToAbbsAndHds");
    }


    @Async("myTaskExecutor")
    public CompletableFuture<Boolean> autoPdfParseToAbbsAndHdProcessDetail(String redisKey, byte[] bytes, int pageCounter, Map<String, Object> detailsMap, boolean abbsAndHdsAsyn) throws IOException {
        log.info("thread : {}-->,正在执行 autoPdfParseToAbbsAndHdProcessDetail", Thread.currentThread().getName());
        log.info("redisKey->{}", redisKey);
        log.info("pageCounter->{}", pageCounter);
        businessAsyncService.autoPdfParseToAbbsAndHdProcessDetailAsyn(redisKey, bytes, pageCounter, detailsMap);
        return CompletableFuture.completedFuture(true);
    }
}

异步业务接口

public interface IBusinessAsyncService {
    boolean autoPdfParseToAbbsAndHdProcess(String allQueryId, byte[] bytes, int pages, boolean abbsAndHdsAsyn) throws IOException;

    void autoPdfParseToAbbsAndHdProcessDetailAsyn(String redisKey, byte[] bytes, int pageCounter, Map<String, Object> detailsMap) throws IOException;

    void autoPdfParseToAbbsAndHdProcessDetailSyn(String redisKey, PDFRenderer pdfRenderer, int pageCounter) throws IOException;

}

异步业务实现

{
    /**
     * 缩略图默认dpi
     */
    private int ABB_IMG_DPI = 72;
    /**
     * 高清图默认dpi
     */
    private int HD_IMG_DPI = 72 * 3;
    /**
     *
     */
    @Value("${plpms-signup.abbsAndHds.expire-time}")
    private long abbsAndHdsExpired;

    @Autowired
    private IRedisService redisService;

    @Autowired
    @Lazy
    private BusinessAsync businessAsync;

    @Autowired
    private IBestSignService bestSignService;
    @Autowired
    private SignUpGlobalConfig signUpGlobalConfig;


    @Override
    public boolean autoPdfParseToAbbsAndHdProcess(String allQueryId, byte[] bytes, int pages, boolean abbsAndHdsAsyn) throws IOException {
        if (StringUtils.isBlank(allQueryId) || bytes == null) {
            return false;
        }

        String redisKey = RedisPreKey.CACHE_PDF_ALLQUERY + allQueryId;

        // 异步任务解析
        if (abbsAndHdsAsyn) {
            long start = System.currentTimeMillis();
            Map<String, Object> map = new ConcurrentHashMap<>(pages * 2 + 5);
            map.put("state", 1);
            map.put("totalPage", pages);
            map.put("processed", 0);
            map.put("time", 0);
            redisService.hmset(redisKey, map);
            redisService.expire(redisKey, abbsAndHdsExpired, TimeUnit.SECONDS);

            Set<Future<Boolean>> set = new HashSet<>(pages);
            for (int pageCounter = 0; pageCounter < pages; pageCounter++) {
                Future<Boolean> booleanFuture = businessAsync.autoPdfParseToAbbsAndHdProcessDetail(redisKey, bytes, pageCounter, map, abbsAndHdsAsyn);
                set.add(booleanFuture);
            }

            while (true) {
                if (set.size() == pages && allDone(set)) {
                    long end = System.currentTimeMillis();
                    map.put("state", 2);
                    map.put("processed", pages);
                    map.put("time", (end - start));
                    redisService.hmset(redisKey, map);


                    log.info("pdf全部解析和上传redis共耗时记录-->uuid:{} , size:{}byte , abbsAndHdsAsyn:{} , pages:{} , time:{}ms , speed:{}ms/p", allQueryId, bytes.length, abbsAndHdsAsyn, pages, (end - start), (end - start) / pages);
                    return true;
                }
            }
            // 同步解析
        } else {
            try (PDDocument document = PDDocument.load(bytes)) {
                long start = System.currentTimeMillis();
                PDFRenderer pdfRenderer = new PDFRenderer(document);
                HashMap<String, Object> map = new HashMap<>(document.getNumberOfPages() * 2 + 5);
                map.put("state", 1);
                map.put("totalPage", document.getNumberOfPages());
                map.put("processed", 0);
                map.put("time", 0);
                redisService.hmset(redisKey, map);


                for (int pageCounter = 0; pageCounter < document.getNumberOfPages(); pageCounter++) {
                    autoPdfParseToAbbsAndHdProcessDetailSyn(redisKey, pdfRenderer, pageCounter);
                }

                long end = System.currentTimeMillis();
                map.put("state", 2);
                map.put("processed", document.getNumberOfPages());
                map.put("time", (end - start));
                redisService.hmset(redisKey, map);


                log.info("pdf全部解析和上传redis共耗时记录-->uuid:{} , size:{}byte , abbsAndHdsAsyn:{} , pages:{} , time:{}ms , speed:{}ms/p", allQueryId, bytes.length, abbsAndHdsAsyn, document.getNumberOfPages(), (end - start), (end - start) / document.getNumberOfPages());
                return true;
            } catch (Exception e) {
                log.error("pdf转换错误:", e);
                return false;
            }

        }
    }

    private boolean allDone(Set<Future<Boolean>> sets) {
        if (sets == null || sets.size() == 0) {
            return false;
        }
        for (Future<Boolean> set : sets) {
            if (!set.isDone()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void autoPdfParseToAbbsAndHdProcessDetailAsyn(String redisKey, byte[] bytes, int pageCounter, Map<String, Object> map) throws IOException {
        try (PDDocument document = PDDocument.load(bytes)) {
            final PDFRenderer pdfRenderer = new PDFRenderer(document);

            long start = System.currentTimeMillis();
            BufferedImage bim = pdfRenderer.renderImageWithDPI(pageCounter, ABB_IMG_DPI, ImageType.RGB);
            ImgToolUtil imgToolUtil = new ImgToolUtil(bim);
            imgToolUtil.resize(ABB_IMG_DPI, ABB_IMG_DPI * bim.getHeight() / bim.getWidth());
            String abb = Base64Util.BufferedImageToBase64(bim);


            BufferedImage bim2 = pdfRenderer.renderImageWithDPI(pageCounter, HD_IMG_DPI, ImageType.RGB);
            ImgToolUtil imgToolUtil2 = new ImgToolUtil(bim2);
            imgToolUtil2.resize(HD_IMG_DPI, HD_IMG_DPI * bim2.getHeight() / bim2.getWidth());
            String hd = Base64Util.BufferedImageToBase64(bim2);

            map.put("abb" + "_" + pageCounter, abb);
            map.put("hd" + "_" + pageCounter, hd);

            long end = System.currentTimeMillis();
            log.info("redisKey->{} , pageCounter->{} , time->{}ms", redisKey, pageCounter, (end - start));
        } catch (Exception e) {
            log.error("解析pdf发生了异常,redisKey->{} , pageCounter->{} , e->{}", redisKey, pageCounter, e);
        }
    }


    @Override
    public void autoPdfParseToAbbsAndHdProcessDetailSyn(String redisKey, PDFRenderer pdfRenderer, int pageCounter) throws IOException {
        try {
            long start = System.currentTimeMillis();
            BufferedImage bim = pdfRenderer.renderImageWithDPI(pageCounter, ABB_IMG_DPI, ImageType.RGB);
            ImgToolUtil imgToolUtil = new ImgToolUtil(bim);
            imgToolUtil.resize(ABB_IMG_DPI, ABB_IMG_DPI * bim.getHeight() / bim.getWidth());
            String abb = Base64Util.BufferedImageToBase64(bim);


            BufferedImage bim2 = pdfRenderer.renderImageWithDPI(pageCounter, HD_IMG_DPI, ImageType.RGB);
            ImgToolUtil imgToolUtil2 = new ImgToolUtil(bim2);
            imgToolUtil2.resize(HD_IMG_DPI, HD_IMG_DPI * bim2.getHeight() / bim2.getWidth());
            String hd = Base64Util.BufferedImageToBase64(bim2);

            Map<String, Object> tmp = new HashMap<>(2);
            tmp.put("abb" + "_" + pageCounter, abb);
            tmp.put("hd" + "_" + pageCounter, hd);
            tmp.put("processed", pageCounter + 1);
            redisService.hmset(redisKey, tmp);

            long end = System.currentTimeMillis();
            log.info("redisKey->{} , pageCounter->{} , time->{}ms", redisKey, pageCounter, (end - start));
        } catch (Exception e) {
            log.error("解析pdf发生了异常,redisKey->{} , pageCounter->{} , e->{}", redisKey, pageCounter, e);
        }
    }

    
}

使用(abbsAndHdsAsyn事全局参数是否异步解析)


businessAsync.autoPdfParseToAbbsAndHds(allQueryId, bytes, document.getNumberOfPages(),abbsAndHdsAsyn);

图片处理工具

/**
 * 图片处理工具
 *
 */
@Setter
@Getter
public class ImgToolUtil {

    protected BufferedImage image;
    protected String type;
    protected boolean alphaed;

    protected ImgToolUtil() {

    }

    protected static InputStream open(String path) throws IOException {
        if (path == null) {
            throw new IOException("path is null");
        }
        return isUrlPath(path) ? new URL(path).openStream() : new FileInputStream(path);
    }

    public ImgToolUtil(BufferedImage image) throws IOException {
        this.image = image;
        this.alphaed = image.getTransparency() == Transparency.TRANSLUCENT;
    }

    public ImgToolUtil(String path) throws IOException {
        this(open(path));
    }

    public ImgToolUtil(InputStream path) throws IOException {
        this(path, null);
    }

    public ImgToolUtil(InputStream path, String type) throws IOException {
        if (path == null) {
            throw new IOException("path is null");
        }
        image = ImageIO.read(path);
        path.close();
        this.type = type;
        this.alphaed = image.getTransparency() == Transparency.TRANSLUCENT;
    }

    public int width() {
        return image.getWidth();
    }

    public int height() {
        return image.getHeight();
    }

    /**
     * 反转
     *
     * @param isHorizontal
     * @return
     */
    public ImgToolUtil flip(boolean isHorizontal) {
        image = getTransformOp(isHorizontal).filter(image, null);
        alphaed = true;
        return this;
    }

    protected AffineTransformOp getTransformOp(boolean isHorizontal) {
        AffineTransform transform;
        if (isHorizontal) {
            transform = new AffineTransform(-1, 0, 0, 1, width(), 0);// 水平翻转 
        } else {
            transform = new AffineTransform(1, 0, 0, -1, 0, height());// 垂直翻转 
        }
        //new AffineTransform(-1, 0, 0, -1, image.getWidth(), image.getHeight());// 旋转180度  
        return new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
    }

    /**
     * 拉伸到目标尺寸
     *
     * @param size
     * @return
     */
    public ImgToolUtil resize(int size) {
        return this.resize(size, size);
    }

    /**
     * 拉伸到目标大小
     *
     * @param width
     * @param height
     * @return
     */
    public ImgToolUtil resize(int width, int height) {
        image = resizeImp(image, width, height, false);
        return this;
    }

    /**
     * 缩略图到目标尺寸
     */
    public ImgToolUtil thumbnail(int size) {
        return this.thumbnail(size, size);
    }

    /**
     * 缩略图到目标大小
     *
     * @param width
     * @param height
     * @return
     */
    public ImgToolUtil thumbnail(int width, int height) {
        image = resizeImp(image, width, height, true);
        return this;
    }

    /**
     * 将图片变成方形(默认以最小边为准)
     */
    public ImgToolUtil square() {
        return this.square(true);
    }

    /**
     * 将图片变为方形
     *
     * @param small
     * @return
     */
    public ImgToolUtil square(boolean small) {
        int w = width();
        int h = height();
        if (w != h) {
            int s = small ? Math.min(w, h) : Math.max(w, h);
            resize(s, s);
        }
        return this;
    }

    /**
     * 图片处理成圆角
     *
     * @param radius 圆角尺寸,比如填10表示10px
     * @return
     */
    public ImgToolUtil corner(int radius) {
        image = cornerImp(image, radius);
        alphaed = true;
        return this;
    }

    /**
     * 图片处理成圆角
     *
     * @return
     */
    public ImgToolUtil corner() {
        return corner(Math.min(width(), height()));
    }

    /**
     * 图片处理成圆角
     *
     * @param percent 圆角比例
     * @return
     */
    public ImgToolUtil corner(float percent) {
        if (percent > 360) {
            percent = percent % 360;
        }
        if (percent > 1) {
            percent = percent / 360;
        }
        float n = Math.min(width(), height()) * percent;
        return corner((int) n);
    }

    /**
     * 图片剪切
     *
     * @param x
     * @param y
     * @param width
     * @param height
     * @return
     */
    public ImgToolUtil cut(int x, int y, int width, int height) {
        image = image.getSubimage(x, y, width, height);
        return this;
    }

    public ImgToolUtil save(String path) throws IOException {
        String format = type == null ? "jpg" : type;
        return save(path, format);
    }

    public ImgToolUtil save(String path, String format) throws IOException {
        if (alphaed) {
            format = "png";
        }
        ImageIO.write(image, format, new File(path));
        return this;
    }

    public ImgToolUtil save(OutputStream path) throws IOException {
        String format = type == null ? "jpg" : type;
        return save(path, format);
    }

    public ImgToolUtil save(OutputStream path, String format) throws IOException {
        if (alphaed) {
            format = "png";
        }
        ImageIO.write(image, format, path);
        return this;
    }

    /**
     * 对图片进行旋转
     *
     * @param degree 0-360
     * @return
     */
    public ImgToolUtil rotate(double degree) {
        image = rotateImp(image, degree);
        alphaed = true;
        return this;
    }

    private static double[] calculatePosition(double x, double y, double angle) {
        double nx = (Math.cos(angle) * x) - (Math.sin(angle) * y);
        double ny = (Math.sin(angle) * x) + (Math.cos(angle) * y);
        return new double[]{nx, ny};
    }

    protected static BufferedImage rotateImp(BufferedImage image, double angle) {
        int width = image.getWidth();
        int height = image.getHeight();
        angle = Math.toRadians(angle);

        double[][] postions = new double[4][];
        postions[0] = calculatePosition(0, 0, angle);
        postions[1] = calculatePosition(width, 0, angle);
        postions[2] = calculatePosition(0, height, angle);
        postions[3] = calculatePosition(width, height, angle);
        double minX = Math.min(
                Math.min(postions[0][0], postions[1][0]),
                Math.min(postions[2][0], postions[3][0])
        );
        double maxX = Math.max(
                Math.max(postions[0][0], postions[1][0]),
                Math.max(postions[2][0], postions[3][0])
        );
        double minY = Math.min(
                Math.min(postions[0][1], postions[1][1]),
                Math.min(postions[2][1], postions[3][1])
        );
        double maxY = Math.max(
                Math.max(postions[0][1], postions[1][1]),
                Math.max(postions[2][1], postions[3][1])
        );
        int newWidth = (int) Math.ceil(maxX - minX);
        int newHeight = (int) Math.ceil(maxY - minY);
        BufferedImage ret = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = ret.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.rotate(angle, newWidth / 2, newHeight / 2);
        int centerX = (int) Math.round((newWidth - width) / 2.0);
        int centerY = (int) Math.round((newHeight - height) / 2.0);
        g.drawImage(image, centerX, centerY, null);
        g.dispose();
        return ret;
    }

    public ImgToolUtil draw(BufferedImage img, Align align) {
        return this.draw(img, align, 0.0d);
    }

    public ImgToolUtil draw(BufferedImage img) {
        return this.draw(img, Align.BOTTOM_RIGHT, 0);
    }

    public ImgToolUtil draw(BufferedImage img, Align align, int offset) {
        return this.draw(img, align, 0.0d, offset, 1.0f);
    }

    public ImgToolUtil draw(BufferedImage img, Align align, Double degree) {
        return this.draw(img, align, degree, 0, 1.0f);
    }

    public ImgToolUtil draw(BufferedImage img, Align align, Double degree, int offset, float alpha) {
        return draw(img, align, degree, offset, offset, alpha);
    }

    public ImgToolUtil draw(String imgPath, Align align) throws IOException {
        return this.draw(ImageIO.read(new File(imgPath)), align);
    }

    public ImgToolUtil draw(String imgPath) throws IOException {
        return this.draw(ImageIO.read(new File(imgPath)));
    }

    public ImgToolUtil draw(String imgPath, Align align, Double degree, int offsetX, int offsetY, float alpha) throws IOException {
        return draw(ImageIO.read(new File(imgPath)), align, degree, offsetX, offsetY, alpha);
    }

    /**
     * 绘制图片水印
     *
     * @param img     图片
     * @param align   位置
     * @param degree  旋转角度
     * @param offsetX 偏移x
     * @param offsetY 偏移y
     * @param alpha   透明度
     * @return
     */
    public ImgToolUtil draw(BufferedImage img, Align align, Double degree, int offsetX, int offsetY, float alpha) {
        int w = img.getWidth(null);
        int h = img.getHeight(null);
        int x;
        int y;
        if (align == Align.BOTTOM_RIGHT) {
            x = (width() - w - offsetX);
            y = (height() - h - offsetY);
        } else if (align == Align.BOTTOM_LEFT) {
            x = offsetX;
            y = (height() - h - offsetY);
        } else if (align == Align.TOP_LEFT) {
            x = offsetX;
            y = offsetY;
        } else if (align == Align.TOP_RIGHT) {
            x = (width() - w - offsetX);
            y = offsetY;
        } else {
            x = (width() - w - offsetX) / 2;
            y = (height() - h - offsetY) / 2;
        }
        if (degree != 0) {
            img = rotateImp(img, degree);
        }
        drawApply(img, x, y, alpha);
        return this;
    }

    protected void drawApply(Image img, int x, int y, float alpha) {
        image = drawIcon(image, img, x, y, alpha);
    }

    public static Image icon(String path) {
        return new ImageIcon(path).getImage();
    }

    protected static BufferedImage drawIcon(BufferedImage image, Image icon, int x, int y, Float alpha) {
        int w = image.getWidth();
        int h = image.getHeight();
        boolean hasAlpha = image.getColorModel().hasAlpha();

        BufferedImage ret = new BufferedImage(w, h, hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);

        Graphics2D g2 = ret.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.drawImage(image, 0, 0, null);
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha == null ? 1.0f : alpha));
        g2.drawImage(icon, x, y, null);
//        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));   
        g2.dispose();
        return ret;
    }

    /**
     * 制作圆角图片(适合圆角头像啥的)
     *
     * @param image
     * @param radius 圆角尺寸
     * @return
     */
    protected static BufferedImage cornerImp(BufferedImage image, int radius) {
        int w = image.getWidth();
        int h = image.getHeight();
        BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2 = output.createGraphics();
        g2.setComposite(AlphaComposite.Src);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(Color.WHITE);
        g2.fill(new RoundRectangle2D.Float(0, 0, w, h, radius, radius));
        g2.setComposite(AlphaComposite.SrcAtop);
        g2.drawImage(image, 0, 0, null);
        g2.dispose();
        return output;
    }

    /**
     * 生成缩略图
     *
     * @param source
     * @param width     目标宽度
     * @param height    目标高度
     * @param ratioKeep 是否等比缩放
     * @return
     */
    protected static BufferedImage resizeImp(BufferedImage source, int width, int height, boolean ratioKeep) {
        BufferedImage target = null;
        double sx = (double) width / source.getWidth();
        double sy = (double) height / source.getHeight();
        if (ratioKeep) {
            if (sx > 1 && sy > 1) {
                return source;
            }
            if (sx < sy) {
                sy = sx;
                height = (int) (sy * source.getHeight());
            } else {
                sx = sy;
                width = (int) (sx * source.getWidth());
            }
        }
        if (source.getType() == BufferedImage.TYPE_CUSTOM) {
            ColorModel cm = source.getColorModel();
            WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
            boolean alphaPremultiplied = cm.isAlphaPremultiplied();
            target = new BufferedImage(cm, raster, alphaPremultiplied, null);
        } else {
            target = new BufferedImage(width, height, source.getType());
        }
        Graphics2D g = target.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
        g.dispose();
        return target;
    }

    private static boolean isUrlPath(String path) {
//        return path.startsWith("http:")||path.startsWith("https:");
        return path.indexOf("://") > 0;
    }

    /**
     * 图标绘制位置
     */
    public static enum Align {
        CENTER,
        BOTTOM_LEFT,
        BOTTOM_RIGHT,
        TOP_LEFT,
        TOP_RIGHT
    }

    /**
     * 根据传入类型变化图片像素
     */
    public static javafx.scene.image.Image pixWithImage(int type, javafx.scene.image.Image image) {
        PixelReader pixelReader = image.getPixelReader();
        if (image.getWidth() > 0 && image.getHeight() > 0) {
            WritableImage wImage;
            wImage = new WritableImage((int) image.getWidth(), (int) image.getHeight());
            PixelWriter pixelWriter = wImage.getPixelWriter();

            for (int y = 0; y < image.getHeight(); y++) {
                for (int x = 0; x < image.getWidth(); x++) {
                    javafx.scene.paint.Color color = pixelReader.getColor(x, y);
                    switch (type) {
                        case 1:
                            // 颜色变轻
                            color = color.brighter();
                            break;
                        case 2:
                            // 颜色变深
                            color = color.darker();
                            break;
                        case 3:
                            // 灰度化
                            color = color.grayscale();
                            break;
                        case 4:
                            // 颜色反转
                            color = color.invert();
                            break;
                        case 5:
                            // 颜色饱和
                            color = color.saturate();
                            break;
                        case 6:
                            // 颜色不饱和
                            color = color.desaturate();
                            break;
                        case 7:
                            // 颜色灰度化后反转(字黑体,背景鲜亮,可用于强字弱景)
                            color = color.grayscale();
                            color = color.invert();
                            break;
                        case 8:
                            // 颜色透明
                            if (color.getOpacity() == 0) {
                                color = new javafx.scene.paint.Color(color.getRed(), color.getGreen(), color.getBlue(), 0);
                            } else {
                                color = new javafx.scene.paint.Color(color.getRed(), color.getGreen(), color.getBlue(), 0.5);
                            }
                            break;
                        default:
                            break;
                    }

                    pixelWriter.setColor(x, y, color);
                }
            }
            return wImage;
        }
        return null;
    }

    public static Color getAwtColor(javafx.scene.paint.Color color) {
        Color colorw = new Color((float) color.getRed(), (float) color.getGreen(), (float) color.getBlue(), (float) color.getOpacity());
        return colorw;
    }
}

踩坑记录

  • 安装过字体后还是报字体的错:我的现象是,在win环境下,单线程同步方法不报错,多线程异步报字体错误,同linux环境报错一样,都是指向字体错误。一开始都是去想办法扩充字体或者论坛上查找的错误都是指向字体,世界上字体那么多,如果是有相关的解决的方案也是不可能百分百避免字体的原因,所以我想着去配置文件,就是字体库映射集关系,会有一个默认的字体,就能解决问题,但实际找到的和运用的代码量颇为复杂,如果是这个jar包的开发人员的话,无论是否付费应该能想到这个是可以配置的,所以我就压根没想着去实现,此时我在逛官网(https://pdfbox.apache.org/)的常见问题中,看到这个我就恍然大悟了:
    在这里插入图片描述
    在这里插入图片描述

原来PDDocument这个对象是线程不安全的,多线程情况下,方法封装不好就会报,所以我就改造了这个方法

可扩展

  • 可以用池技术,再次优化替换document.close(),由于当前版本不支持池化,等待后期官方
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java 通过 pdfbox 库可以实现图片PDF 的功能,同时也可以通过该库对 PDF 中的图片进行压缩。以下是一个示例代码: ```java import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import java.io.File; import java.io.IOException; public class ImageToPdf { public static void main(String[] args) throws IOException { // 创建一个空白的PDF文档 PDDocument document = new PDDocument(); // 加载要转换为PDF图片文件 File imageFile = new File("image.jpg"); // 创建一个页面对象 PDPage page = new PDPage(); // 将图片转换为PDF图像对象 PDImageXObject image = JPEGFactory.createFromImage(document, ImageIO.read(imageFile), 0.5f); // 在页面上添加图像 page.getCropBox(); page.setMediaBox(image.getCropBox()); page.setRotation(0); page.setArtBox(image.getCropBox()); page.setBleedBox(image.getCropBox()); page.setTrimBox(image.getCropBox()); PDPageContentStream contentStream = new PDPageContentStream(document, page); contentStream.drawImage(image, 0, 0); // 关闭页面内容流 contentStream.close(); // 将页面添加到文档中 document.addPage(page); // 保存PDF文件 document.save("image.pdf"); // 关闭PDF文档 document.close(); } } ``` 如果需要在压缩 PDF 中的图片,可以使用下面的代码对 PDF 中的所有图片进行压缩: ```java import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import java.io.File; import java.io.IOException; public class CompressPdfImages { public static void main(String[] args) throws IOException { // 加载PDF文件 PDDocument document = PDDocument.load(new File("input.pdf")); // 遍历所有页面 for (PDPage page : document.getPages()) { // 获取页面中的所有图像 for (PDImageXObject image : page.getResources().getXObjectNames().stream() .map(x -> page.getResources().getXObject((COSName) x)) .filter(x -> x instanceof PDImageXObject) .map(x -> (PDImageXObject) x) .toArray(PDImageXObject[]::new)) { // 如果图像是JPEG格式,则进行压缩 if ("jpg".equals(image.getSuffix())) { PDImageXObject compressedImage = JPEGFactory.createFromImage(document, image.getImage(), 0.5f); page.getResources().add(compressedImage); page.getResources().remove(image.getName()); } // 如果图像是PNG格式,则进行压缩 else if ("png".equals(image.getSuffix())) { PDImageXObject compressedImage = LosslessFactory.createFromImage(document, image.getImage(), 0.5f); page.getResources().add(compressedImage); page.getResources().remove(image.getName()); } } } // 保存压缩后的PDF文件 document.save("output.pdf"); // 关闭PDF文档 document.close(); } } ``` 如果需要将多个 PDF 文件合并为一个 PDF 文件,可以使用下面的代码: ```java import org.apache.pdfbox.multipdf.PDFMergerUtility; import java.io.File; import java.io.IOException; public class MergePdfFiles { public static void main(String[] args) throws IOException { // 创建一个PDF合并工具对象 PDFMergerUtility merger = new PDFMergerUtility(); // 添加要合并的PDF文件 merger.addSource(new File("file1.pdf")); merger.addSource(new File("file2.pdf")); merger.addSource(new File("file3.pdf")); // 合并PDF文件 merger.setDestinationFileName("merged.pdf"); merger.mergeDocuments(null); } } ``` 以上是 Java 中使用 pdfbox实现图片PDF压缩 PDF图片、合并多个 PDF 文件为一个 PDF 文件的示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若光672

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值