研究pgm转png比较消耗内存的问题
- 2020.01.14 通过Java的ImageIO生成png与jpg图片,5000*5000的pgm需要消耗300M内存以上
- 2020.01.15 通过jconsole, mat等工具,定位问题是由于在内存中生成整个png所有需要的数据,导致的问题
- 2020.01.16 研究方向为根据png的实际文件结构,自己写代码生成png图片,基于昨天的研究:png支持灰度图
使用jdk12测试:
- 将5000*5000的pgm转换为png消耗12M左右
- 将10000*10000的pgm转换为png消耗14M左右
由于需要运行java类库,实际使用估计在2M左右
如果连续转换,由于java的垃圾回收机制,可能会由于一些垃圾导致内存占用变大
可以通过设置垃圾回收解决
代码实现说明
- 通过调试ImageIO生成BufferedImage.TYPE_BYTE_GRAY的PNG图片的代码,研究生成PNG的过程
- 只保留必须的png文件结构:magic、IHDR、IDAT、IEND
- 具体实现,拷贝了com.sun.imageio.plugins.png的实现
执行pgm转png
java -jar pgm-to-png.jar pgm=5000-5000.pgm
参数说明:
- pgm: 源pgm文件地址,若只有文件名,则为运行时目录
- png: 转换后的png文件地址,若为空,则将pgm文件路径换成png后缀为png文件地址
- start: 启动倒数秒数
- end: 结束倒数秒数
使用jconsole监控内存使用情况
设置为1秒采样一次:命令行执行jconsole -interval=1
- 使用远程连接,方便重试
java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7077 -Dcom.sun.management.jmxremote.local.only=true -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -jar pgm-to-png.jar pgm=5000-5000.pgm start=5
- 本地在jconsole中,直接选择进行ID也可以,多次执行不太方便
创建pgm文件
java -cp pgm-to-png.jar com.bdr.demo.PgmCreator
无参数默认创建5000*5000的文件
参数说明:
- 灰度图长度
- height: 灰度图高度
- file: 灰度图保存地址
示例:
java -cp pgm-to-png.jar com.bdr.demo.PgmCreator width=100 height=100 file=test.pgm
代码
PgmToPng
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class PgmToPng {
public static void main(String[] args) throws Exception {
Map<String, String> argMap = new HashMap<>();
for (String arg : args) {
String[] argArr = arg.split("=");
argMap.put(argArr[0], argArr[1]);
}
String source = argMap.get("pgm");
if (!Files.isRegularFile(Paths.get(source))) {
System.out.println("source file not exist: " + source);
return;
}
String dest = argMap.get("png");
dest = dest == null ? source.substring(0, source.lastIndexOf('.')) + ".png" : dest;
if (Files.isRegularFile(Paths.get(dest))) {
System.out.println("delete file: " + dest);
Files.deleteIfExists(Paths.get(dest));
}
System.out.println("source file: " + source);
System.out.println("dest file: " + dest);
System.out.println("start...");
countDown(argMap.get("start"));
new PgmToPng().convertPgmToPng(source, dest);
System.out.println("convert over: " + dest);
System.out.println("end.");
countDown(argMap.get("end"));
}
private static void countDown(String secondStr) throws Exception {
if (secondStr == null) {
return;
}
try {
countDown(Integer.parseInt(secondStr));
} catch (Exception e) {
e.printStackTrace();
}
}
private static void countDown(int second) throws Exception {
for (int t = second; t > 0; t--) {
TimeUnit.SECONDS.sleep(1);
System.out.println(t + "s...");
}
}
private void convertPgmToPng(String source, String dest) throws Exception {
final BufferedInputStream in = new BufferedInputStream(new FileInputStream(source));
ImageOutputStream out = new FileImageOutputStream(new File(dest));
try (in; out) {
if (!"P5".equals(next(in))) {
throw new IOException("File is not a binary PGM image.");
}
final int col = Integer.parseInt(next(in));
final int row = Integer.parseInt(next(in));
final int max = Integer.parseInt(next(in));
if (max < 0 || max > 255) {
throw new IOException("The image's maximum gray value must be in range [0, " + 255 + "].");
}
writeMagic(out);
writeIHDR(out, col, row);
IDATOutputStream os = new IDATOutputStream(out, 32768, 4);
byte[] curRow = new byte[col];
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
final int p = in.read();
if (p < 0 || p > max) {
throw new IOException("Pixel value " + p + " outside of range [0, " + max + "].");
}
curRow[j] = (byte) p;
}
writeIDAT(os, curRow, col);
os.flush();
}
os.finish();
writeIEND(out);
out.flush();
}
}
protected void writeMagic(ImageOutputStream stream) throws Exception {
byte[] magic = {(byte) 137, 80, 78, 71, 13, 10, 26, 10};
stream.write(magic);
}
protected void writeIHDR(ImageOutputStream stream, int col, int row) throws Exception {
ChunkStream cs = new ChunkStream(0x49484452, stream); // PNGImageReader.PNGImageReader.IHDR_TYPE
cs.writeInt(col); // IHDR_width
cs.writeInt(row); // IHDR_height
cs.writeByte(8); // IHDR_bitDepth
cs.writeByte(0); // IHDR_colorType
cs.writeByte(0); // IHDR_compressionMethod
cs.writeByte(0); // IHDR_filterMethod
cs.writeByte(0); // IHDR_interlaceMethod
cs.finish();
}
protected void writeIDAT(ImageOutputStream os, byte[] currRow, int col) throws Exception {
os.write(0);
os.write(currRow, 0, col);
}
protected void writeIEND(ImageOutputStream stream) throws Exception {
ChunkStream cs = new ChunkStream(0x49454e44, stream);
cs.finish();
}
private static String next(final InputStream stream) throws IOException {
final List<Byte> bytes = new ArrayList<>();
while (true) {
final int b = stream.read();
if (b != -1) {
final char c = (char) b;
if (c == '#') {
int d;
do {
d = stream.read();
} while (d != -1 && d != '
' && d != '
');
} else if (!Character.isWhitespace(c)) {
bytes.add((byte) b);
} else if (!bytes.isEmpty()) {
break;
}
} else {
break;
}
}
final byte[] bytesArray = new byte[bytes.size()];
for (int i = 0; i < bytesArray.length; ++i)
bytesArray[i] = bytes.get(i);
return new String(bytesArray);
}
}
PgmCreator
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class PgmCreator {
public static void main(String[] args) throws Exception {
Map<String, String> argMap = new HashMap<>();
for (String arg : args) {
String[] argArr = arg.split("=");
argMap.put(argArr[0], argArr[1]);
}
String widthStr = argMap.get("width");
int width = widthStr == null ? 5000 : Integer.parseInt(widthStr);
String heightStr = argMap.get("height");
int height = heightStr == null ? 5000 : Integer.parseInt(heightStr);
String file = argMap.get("file");
file = file == null ? width + "-" + height + ".pgm" : file;
PGMIO.write(PGMUtils.createArc(width, height), new File(file));
}
}