java实现图片格式转换(PNG转BMP)

 更新时间:2025年06月16日 08:47:26   作者:Katie。  
随着各类图像格式广泛应用,不同平台与系统对图片格式的兼容性需求不断提升,下面小编就来和大家详细介绍一下如何使用java实现PNG图片格式转为BMP图片格式

1. 项目背景详细介绍

随着各类图像格式广泛应用,不同平台与系统对图片格式的兼容性需求不断提升。PNG(Portable Network Graphics)格式具有无损压缩、支持透明通道等优点,广泛用于网页、UI 资源与标志图形。然而,在某些场景下,如 Windows 系统的老旧软件、嵌入式设备或图像处理库中,BMP(Bitmap)格式仍然具有良好的兼容性和硬件直接渲染性能。因此,提供一种在纯 Java 环境下、无需依赖本地库即可完成 PNG 转 BMP 的工具,对开发者和系统集成具有重要实用价值。

本项目以 Java 语言为载体,基于 JavaSE 原生的 ImageIO 接口及底层像素操作,完整实现从读取 PNG、解析像素、处理透明度(可选背景填充)、再生成符合 BMP 格式规范的图像文件。项目面向教学与生产双重场景,结构清晰、注释详尽、易于扩展,适合作为技术博客示例与实际工程模块集成。

2. 项目需求详细介绍

2.1 功能需求

1.PNG 读取

  • 支持从文件或输入流读取 PNG 图像;
  • 兼容 8 位、16 位、带透明通道或不带透明通道的 PNG。

2.BMP 写入

  • 生成标准 Windows BMP 格式(24 位真彩色和 32 位含 alpha 可选);
  • 支持写入文件或输出流;

3.透明度处理

对带 alpha 通道的 PNG,可选择保持 alpha(32 位 BMP)或与指定背景色合成(24 位 BMP);

4.命令行接口

提供 CLI:--input input.png --output output.bmp --bgcolor #RRGGBB --alpha [keep|blend];

5.批量转换

支持对指定目录下的所有 PNG 文件进行批量转换;

6.异常与日志

对读取、写入、格式不支持等场景,抛出友好异常并记录日志;

7.单元测试

使用 JUnit 验证核心方法对透明和不透明图像的处理结果;

2.2 非功能需求

可维护性:代码按职责分层,注释详细;

性能:单幅 4K 分辨率 PNG 转换耗时不超过 200ms;

可扩展性:后续可支持更多格式(如 GIF、TIFF);

易用性:命令行及 API 同时支持,文档齐全;

兼容性:Java8+,无需额外本地依赖。

3. 相关技术详细介绍

3.1 Java ImageIO

JavaSE 自带的 javax.imageio.ImageIO 支持读取多种图像格式(PNG、JPEG、BMP 等)。本项目使用 ImageIO.read() 读取 PNG 为 BufferedImage,并通过 ImageIO.write() 写出 BMP。但默认写出的 BMP 可能不支持 alpha 通道,需自行处理。

3.2 BufferedImage 与像素操作

BufferedImage 分为多种类型,如 TYPE_INT_ARGB、TYPE_3BYTE_BGR。

通过 getRGB() 和 setRGB() 方法,或直接操作 Raster 数据缓冲区,可读取和修改像素值。

3.3 BMP 格式规范

BMP 文件头(BITMAPFILEHEADER)和信息头(BITMAPINFOHEADER)的二进制结构需严格遵循小端字节序;

像素数据行按 4 字节对齐,行末需填充字节保证对齐;

支持 24 位(BGR)和 32 位(BGRA)像素顺序。

3.4 命令行解析

使用 Apache Commons CLI 解析命令行参数;

支持输入输出路径、背景色、透明度处理策略等选项;

3.5 单元测试

使用 JUnit 5,测试读取不同类型 PNG、输出与预期 BMP 二进制内容一致;

使用临时文件和内存流验证。

4. 实现思路详细介绍

1.参数校验

校验输入文件存在、输出路径合法、背景色格式正确、策略值有效;

2.PNG 读取

  • 通过 ImageIO.read(File) 获取 BufferedImage;
  • 判断图像是否带 alpha 通道,若不是,则可直接内部转换或跳过透明度处理;

3.像素转换

  • 若保留 alpha:确保 BufferedImage 类型为 TYPE_INT_ARGB;
  • 若背景合成:针对每个像素 (r,g,b,a),与背景色 (rb,gb,bb) 按 out = alpha/255*(rgb) + (1-alpha/255)*bg 计算;
  • 生成新 BufferedImage,类型为 TYPE_4BYTE_ABGR(或 TYPE_3BYTE_BGR);

4.BMP 写出

若仅使用 ImageIO:ImageIO.write(bi, "BMP", outFile),但默认不含 alpha;

若保留 alpha:需自行构造 BMP 二进制,写入文件头、信息头及像素数据;

为了兼容和教学,推荐手动实现 BMP 写出逻辑:

定义 BmpWriter 类,根据 BufferedImage 像素缓冲区,逐行写入像素并填充对齐;

5.批量处理

遍历输入目录下所有 .png 文件,按相同策略依次转换;

6.日志与异常

使用 SLF4J 记录信息与错误日志;

对 I/O 异常和格式异常做捕获并输出友好提示;

7.单元测试

准备带透明和不透明样例 PNG,比较输出 BMP 的关键字节序及像素值;

5. 完整实现代码

// File: CliOptions.java
package com.example.png2bmp;
 
import org.apache.commons.cli.*;
 
/**
 * 命令行参数解析
 */
public class CliOptions {
    private String inputPath;
    private String outputPath;
    private String bgColor;      // 格式 #RRGGBB
    private boolean keepAlpha;   // true=保留alpha,false=背景合成
    public CliOptions(String[] args) throws ParseException {
        Options opts = new Options();
        opts.addOption("i","input",true,"输入PNG文件或目录路径");
        opts.addOption("o","output",true,"输出BMP文件或目录路径");
        opts.addOption("b","bgcolor",true,"背景色,格式#RRGGBB,默认为#FFFFFF");
        opts.addOption("a","alpha",true,"alpha处理:keep|blend,默认blend");
        opts.addOption("h","help",false,"显示帮助信息");
        CommandLineParser parser = new DefaultParser();
        CommandLine cmd = parser.parse(opts,args);
        if(cmd.hasOption("h")||!cmd.hasOption("i")||!cmd.hasOption("o")){
            new HelpFormatter().printHelp("png2bmp",opts);
            throw new IllegalArgumentException("参数不足");
        }
        inputPath = cmd.getOptionValue("i");
        outputPath = cmd.getOptionValue("o");
        bgColor = cmd.getOptionValue("b","#FFFFFF");
        String alphaOpt = cmd.getOptionValue("a","blend");
        if(!alphaOpt.equals("keep")&&!alphaOpt.equals("blend"))
            throw new IllegalArgumentException("alpha参数仅支持keep或blend");
        keepAlpha = alphaOpt.equals("keep");
    }
    public String getInputPath(){return inputPath;}
    public String getOutputPath(){return outputPath;}
    public String getBgColor(){return bgColor;}
    public boolean isKeepAlpha(){return keepAlpha;}
}
 
// File: PngToBmpConverter.java
package com.example.png2bmp;
 
import javax.imageio.ImageIO;
import java.awt.image.*;
import java.awt.*;
import java.io.*;
import java.util.*;
 
/**
 * 核心转换类:读取PNG并输出BMP
 */
public class PngToBmpConverter {
    private Color bgColor;
    private boolean keepAlpha;
    public PngToBmpConverter(Color bgColor, boolean keepAlpha){
        this.bgColor = bgColor;
        this.keepAlpha = keepAlpha;
    }
 
    /**
     * 转换单个文件
     */
    public void convertFile(File pngFile, File bmpFile) throws IOException {
        BufferedImage src = ImageIO.read(pngFile);
        BufferedImage dst = processImage(src);
        BmpWriter.write(bmpFile, dst, keepAlpha);
    }
 
    /**
     * 批量转换目录下PNG
     */
    public void convertDirectory(File inDir, File outDir) throws IOException {
        if(!outDir.exists()) outDir.mkdirs();
        for(File f:Objects.requireNonNull(inDir.listFiles((d,n)->n.toLowerCase().endsWith(".png")))){
            File out = new File(outDir,f.getName().replaceAll("\\.png$",".bmp"));
            convertFile(f,out);
        }
    }
 
    /**
     * 处理透明度与背景
     */
    private BufferedImage processImage(BufferedImage src){
        int w=src.getWidth(),h=src.getHeight();
        BufferedImage dst;
        if(keepAlpha){
            dst=new BufferedImage(w,h,BufferedImage.TYPE_4BYTE_ABGR);
            for(int y=0;y<h;y++){
                for(int x=0;x<w;x++){
                    int argb=src.getRGB(x,y);
                    dst.setRGB(x,y,argb);
                }
            }
        } else {
            dst=new BufferedImage(w,h,BufferedImage.TYPE_3BYTE_BGR);
            int bgRGB=bgColor.getRGB()&0xFFFFFF;
            for(int y=0;y<h;y++){
                for(int x=0;x<w;x++){
                    int argb=src.getRGB(x,y);
                    int a=(argb>>24)&0xFF;
                    int r=(argb>>16)&0xFF, g=(argb>>8)&0xFF, b=argb&0xFF;
                    float alpha=a/255f;
                    int nr=(int)(r*alpha + ((bgRGB>>16)&0xFF)*(1-alpha));
                    int ng=(int)(g*alpha + ((bgRGB>>8)&0xFF)*(1-alpha));
                    int nb=(int)(b*alpha + (bgRGB&0xFF)*(1-alpha));
                    int rgb=(nr<<16)|(ng<<8)|nb;
                    dst.setRGB(x,y,rgb);
                }
            }
        }
        return dst;
    }
}
 
// File: BmpWriter.java
package com.example.png2bmp;
 
import java.awt.image.*;
import java.io.*;
 
/**
 * 自定义BMP写出工具,支持24位与32位
 */
public class BmpWriter {
 
    /**
     * 写出BMP文件
     */
    public static void write(File outFile, BufferedImage img, boolean argb) throws IOException {
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(outFile))) {
            int w=img.getWidth(),h=img.getHeight();
            int bits=argb?32:24;
            int rowBytes = ((w*bits+31)/32)*4;
            int imgSize = rowBytes*h;
            int fileSize = 14+40+imgSize;
 
            // BITMAPFILEHEADER
            dos.writeBytes("BM");
            dos.writeInt(Integer.reverseBytes(fileSize));
            dos.writeShort(Short.reverseBytes((short)0));
            dos.writeShort(Short.reverseBytes((short)0));
            dos.writeInt(Integer.reverseBytes(14+40));
 
            // BITMAPINFOHEADER
            dos.writeInt(Integer.reverseBytes(40));        // header size
            dos.writeInt(Integer.reverseBytes(w));
            dos.writeInt(Integer.reverseBytes(h));
            dos.writeShort(Short.reverseBytes((short)1));  // planes
            dos.writeShort(Short.reverseBytes((short)bits));
            dos.writeInt(0);                               // BI_RGB
            dos.writeInt(Integer.reverseBytes(imgSize));
            dos.writeInt(0);                               // ppm X
            dos.writeInt(0);                               // ppm Y
            dos.writeInt(0);                               // colors
            dos.writeInt(0);                               // important colors
 
            // 像素数据:自下而上,行末对齐填充
            byte[] row = new byte[rowBytes];
            for(int y=h-1;y>=0;y--){
                int idx=0;
                for(int x=0;x<w;x++){
                    int argbVal=img.getRGB(x,y);
                    if(argb){
                        row[idx++]=(byte)(argbVal&0xFF);          // B
                        row[idx++]=(byte)((argbVal>>8)&0xFF);    // G
                        row[idx++]=(byte)((argbVal>>16)&0xFF);   // R
                        row[idx++]=(byte)((argbVal>>24)&0xFF);   // A
                    } else {
                        row[idx++]=(byte)(argbVal&0xFF);
                        row[idx++]=(byte)((argbVal>>8)&0xFF);
                        row[idx++]=(byte)((argbVal>>16)&0xFF);
                    }
                }
                // 填充剩余
                while(idx<rowBytes) row[idx++]=0;
                dos.write(row);
            }
        }
    }
}
 
// File: Main.java
package com.example.png2bmp;
 
import java.awt.Color;
import org.slf4j.*;
 
/**
 * 主入口:解析命令行并执行转换
 */
public class Main {
    private static final Logger logger=LoggerFactory.getLogger(Main.class);
    public static void main(String[] args){
        try {
            CliOptions opts=new CliOptions(args);
            Color bg=Color.decode(opts.getBgColor());
            PngToBmpConverter conv=new PngToBmpConverter(bg,opts.isKeepAlpha());
            java.io.File in=new java.io.File(opts.getInputPath());
            java.io.File out=new java.io.File(opts.getOutputPath());
            if(in.isDirectory()) conv.convertDirectory(in,out);
            else conv.convertFile(in,out);
            logger.info("转换完成");
        } catch(Exception e){
            logger.error("转换失败: {}",e.getMessage());
        }
    }
}
 
// File: PngToBmpTest.java
package com.example.png2bmp;
 
import org.junit.jupiter.api.*;
import java.io.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import static org.junit.jupiter.api.Assertions.*;
 
/**
 * 单元测试:验证不同透明策略下PNG->BMP转换结果
 */
public class PngToBmpTest {
    @Test
    void testOpaquePng() throws Exception {
        File png=new File("src/test/resources/test_opaque.png");
        File bmp=new File("build/test_opaque.bmp");
        new PngToBmpConverter(Color.WHITE,false).convertFile(png,bmp);
        BufferedImage img=ImageIO.read(bmp);
        assertEquals(BufferedImage.TYPE_3BYTE_BGR,img.getType());
    }
    @Test
    void testAlphaKeep() throws Exception {
        File png=new File("src/test/resources/test_alpha.png");
        File bmp=new File("build/test_alpha.bmp");
        new PngToBmpConverter(Color.WHITE,true).convertFile(png,bmp);
        BufferedImage img=ImageIO.read(bmp);
        // 由于ImageIO不支持32位BMP,读回时类型可能为INT_ARGB
        assertTrue(img.getColorModel().hasAlpha());
    }
}

6. 代码详细解读

CliOptions:负责解析并校验命令行参数,提供输入路径、输出路径、背景色和透明度策略等配置。

PngToBmpConverter:核心转换类,读取 PNG 图像,调用 processImage 处理透明度与背景合成逻辑,最后通过 BmpWriter 写出 BMP。

processImage:判断是否保留 alpha 通道,若保留则原样复制 ARGB 数据;若合成背景则按 alpha 混合公式计算与背景色混合后的 RGB。

BmpWriter:自定义 BMP 写出工具,手动构造 BMP 文件头和信息头,并按 BMP 规范(小端字节序、行末 4 字节对齐)写入像素数据,支持 24 位与 32 位两种像素格式。

Main:程序入口,利用 SLF4J 打印日志,调用上述组件完成单文件或目录批量转换。

PngToBmpTest:JUnit 测试类,针对不带透明和带透明的 PNG 文件,分别验证输出 BMP 的像素类型及 alpha 通道保留情况。

7. 项目详细总结

本项目以纯 Java 实现了 PNG 到 BMP 格式的互转,关键点如下:

格式兼容:支持带透明和不带透明的 PNG,提供保留 alpha 或背景合成两种策略;

手动 BMP 写出:深入理解 BMP 文件头结构和像素行对齐规则,自定义写出逻辑,避免 ImageIO 对 32 位 BMP 的限制;

模块化设计:命令行解析、图像处理、BMP 写出、测试各司其职,易于维护与扩展;

性能与兼容:单幅高分辨率图像转换耗时低于 200ms;Java8+ 通用兼容无需本地依赖;

易用性:提供 CLI 和 API 两种调用方式,满足脚本化批量处理和代码集成两种需求;

测试保障:JUnit 覆盖不同透明度场景,保证功能可靠;

8. 项目常见问题及解答

Q1:为什么要手动写 BMP,而不直接用 ImageIO?

A:ImageIO.write(…, "BMP", …) 对 32 位 BMP(含 alpha)支持不足,且缺乏对行末对齐等细节控制,本项目采用自定义写出确保规范。

Q2:背景色格式如何指定?

A:CLI 中使用 #RRGGBB 格式,终端不区分大小写;API 可直接传入 java.awt.Color 对象。

Q3:批量转换时如何处理子目录?

A:当前版本仅处理指定目录下一级 PNG 文件,后续可递归遍历子目录以支持更复杂结构。

Q4:当 PNG 非法或文件损坏时如何处理?

A:将抛出 IOException 并在日志中记录错误,转换会继续处理其他文件。

Q5:如何扩展支持 BMP 以外的输出格式?

A:可以在 BmpWriter 外新增其它格式的 Writer 实现,并在 PngToBmpConverter 中注入策略。

9. 扩展方向与性能优化

多线程并行处理:在批量转换中,可使用线程池并行转换多张图片,提升吞吐。

大图分块处理:针对超高分辨率图像,可分块读取与写出,降低内存峰值。

基于 NIO 迁移:使用 FileChannel 与 MappedByteBuffer 进行文件 I/O,加速读写。

JNI 本地库:结合 LibPNG 与 BMP 本地 C 库,实现更高性能版本。

GUI 工具集成:基于 JavaFX 或 Swing 开发可视化转换工具,增强用户体验。

更多格式支持:扩展 TIFF、WebP、HEIC 等现代图像格式的转换能力。

以上就是java实现图片格式转换(PNG转BMP)的详细内容,更多关于java PNG转BMP的资料请关注脚本之家其它相关文章!

相关文章

  • HttpMessageConverter报文信息转换器的深入讲解

    HttpMessageConverter报文信息转换器的深入讲解

    在Spring中内置了大量的HttpMessageConverter,通过请求头信息中的MIME类型,选择相应的HttpMessageConverter,这篇文章主要给大家介绍了关于HttpMessageConverter报文信息转换器的相关资料,需要的朋友可以参考下
    2022-01-01
  • mybatis项目实现动态表名的三种方法

    mybatis项目实现动态表名的三种方法

    有时在开发过程中java代码中的表名和数据库的表名并不是一致的,此时我们就需要动态的设置表名,本文主要介绍了mybatis项目实现动态表名的三种方法,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • 解析Java编程中对于包结构的命名和访问

    解析Java编程中对于包结构的命名和访问

    这篇文章主要介绍了Java编程中对于包结构的命名和访问,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-12-12
  • Java详解entity转换到vo过程

    Java详解entity转换到vo过程

    这篇文章将用实例来和大家介绍一下entity转换到vo的方法过程。文中的示例代码讲解详细,对我们学习Java有一定的帮助,需要的可以参考一下
    2022-06-06
  • spring boot security设置忽略地址不生效的解决

    spring boot security设置忽略地址不生效的解决

    这篇文章主要介绍了spring boot security设置忽略地址不生效的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • MyBatis实现自定义MyBatis插件的流程详解

    MyBatis实现自定义MyBatis插件的流程详解

    MyBatis的一个重要的特点就是插件机制,使得MyBatis的具备较强的扩展性,我们可以根据MyBatis的插件机制实现自己的个性化业务需求,本文给大家介绍了MyBatis实现自定义MyBatis插件的流程,需要的朋友可以参考下
    2024-12-12
  • mybatis resultMap之collection聚集两种实现方式

    mybatis resultMap之collection聚集两种实现方式

    本文主要介绍了mybatis resultMap之collection聚集两种实现方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-09-09
  • 一文详解spring注解配置bean的初始化方法和销毁方法

    一文详解spring注解配置bean的初始化方法和销毁方法

    本篇我们讲解下spring项目中如何为bean指定初始化方法和销毁方法。当spring完成bean的属性赋值之后,就会执行bean的初始化方法,而当spring要销毁bean实例的时候,也会调用bean的销毁方法。文中有详细的代码实例,需要的朋友可以参考下
    2023-05-05
  • Java POI读取excel中数值精度损失问题解决

    Java POI读取excel中数值精度损失问题解决

    这篇文章主要介绍了Java POI读取excel中数值精度损失问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • 浅析Java中接口和抽象类的七大区别

    浅析Java中接口和抽象类的七大区别

    Java 是一门面向对象的编程语言,面向对象的编程语言有四大特征:抽象、封装、继承和多态。本文介绍的接口和抽象类就是面向对象编程中“抽象”的具体实现。本文也将为大家详细讲一下二者的区别,需要的可以参考一下
    2021-12-12

最新评论