图片和ppm文件互转

一、代码结构

二、代码实现

Denoise.java:

package com.xj.ppm.toimg;

import java.awt.FlowLayout;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

/**
 * Image noise reduction program via image stacking.
 * 
 * <p>
 * Uncomment one pair of dir/prefix lines to denoise different series of images.
 * The images are located in the img folder of the project.
 *
 */
public class Denoise {

	/**
	 * Entry point of the program.
	 * 
	 * @param args not used
	 */
	public static void main(String[] args) {
		String dir1 = "main/resources/img/";

		// cone nebula
		//String dir2 = "1";
		//String prefix = "cone_nebula";

		// N44F
		//String dir2 = "2";
		//String prefix = "n44f";
		
		// Orion nebula
		//String dir2 = "3";
		//String prefix = "orion";
		
		// Carina nebula
		//String dir2 = "4";
		//String prefix = "wfc3_uvis";

		// wallpaper
		String dir2 = "wallpaper";
		String prefix = "wallpaper";

		//分成10个ppm文件
		int n = 10;
		PpmStacker stacker = new PpmStacker(dir1 + dir2, prefix, n);
		
		JFrame frame = new JFrame();
		frame.getContentPane().setLayout(new FlowLayout());
		//将stacker.avg转成BufferedImage
		BufferedImage buf = stacker.getAverage().asBufferedImage();
		//向frame中添加图像
		frame.getContentPane().add(new JLabel(new ImageIcon(buf)));
		//根据窗口里面的布局及组件的preferredSize来确定frame的最佳大小
		frame.pack();
		//显示frame
		frame.setVisible(true);
		/**
		 * 点击窗口右上角关闭,四种关闭方式:
		 * 		1.DO_NOTHING_ON_CLOSE,(在你点击关闭按钮的时候,不会被关闭,)不执行任何操作。
		 * 		2.HIDE_ON_CLOSE,(当你点击关闭按钮的时候,不会释放内存,只是隐藏该界面,没有真正的关闭,还占有资源)只隐藏界面,setVisible(false)。
		 * 		3.DISPOSE_ON_CLOSE,点击关闭按钮的时候,隐藏并释放窗体,dispose(),当最后一个窗口被释放后,则程序也随之运行结束。
		 * 		4.EXIT_ON_CLOSE,直接关闭应用程序,System.exit(0)。一个main函数对应一整个程序。
		 */
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

}

PpmAsciiReader.java:

package com.xj.ppm.toimg;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

/**
 * A reader of PPM ASCII images. This reader can handle blank lines and full
 * line comments, but cannot handle comments that appear on the same line as
 * image data. This reader has other limitations as described below.
 * 
 * <p>
 * The image header must be equal to {@code P3}, otherwise an exception is
 * thrown.
 * 
 * <p>
 * The image width and height must appear on the same line, otherwise an
 * exception is thrown.
 * 
 * <p>
 * The maximum value for each color must appear on a separate line, otherwise a
 * read error is likely to occur when reading in the pixel color data.
 * 
 * <p>
 * Each line of the image pixel data must contain a multiple of 3 unsigned
 * integer values.
 *
 */
public class PpmAsciiReader {

	//图像的magicNumber
	private String magicNumber;
	//图像的高
	private int height;
	//图像的宽
	private int width;
	//图像的maxValue
	private int maxValue;
	private SimpleImage img;

	/**
	 * Initializes a reader object that reads the PPM file located in the specified
	 * directory having the specified filename. If the specified file is readable
	 * then this constructor attempts to read the file as though it were a PPM
	 * plaintext color image file.
	 * 
	 * @param dir      the directory containing the PPM file
	 * @param filename the filename of the PPM file
	 * @throws PpmException if the PPM file is not readable, or if the file is
	 *                      readable but contains invalid or unexpected data
	 */
	public PpmAsciiReader(String dir, String filename) {
		
		//根据src、dir和filename拼接出文件路径
		Path path = FileSystems.getDefault().getPath("src", dir, filename);
		List<String> lines = null;
		try {
			//读取指定文件的所有行
			lines = Files.readAllLines(path);
		} catch (IOException x) {
			x.printStackTrace();
			throw new PpmException(x.getMessage());
		}
		//读取magic number
		List<String> next = readMagicNumber(lines);
		//跳过空格和注释
		next = skipBlanksAndComments(next);
		//获取图片的width和height
		next = readSize(next);
		//跳过空格和注释
		next = skipBlanksAndComments(next);
		//获取max value,即最大颜色值
		next = readMaxValue(next);
		//初始化一个SimpleImage
		this.img = new SimpleImage(this.width, this.height);
		//跳过空格和注释
		next = skipBlanksAndComments(next);
		//读取数据填充到pixels中
		readPixels(next);
	}

	/**
	 * Attempts to read the magic number from the PPM file returning the remaining
	 * lines of the file.
	 * 
	 * @param lines the currently unread lines of text of the PPM file
	 * @return the remaining lines of text after reading the magic number
	 * @throws PpmException if the magic number is missing or is not equal to "P3"
	 */
	private List<String> readMagicNumber(List<String> lines) {
		//如果文件为空,则报异常
		if (lines.isEmpty()) {
			throw new PpmException("missing magic number");
		}
		//获取magic number,默认都是在第一行  trim()删除字符串的首尾空格
		this.magicNumber = lines.get(0).trim();
		//如果不是P3则不为正常文件
		if (!this.magicNumber.equals("P3")) {
			throw new PpmException("unexpected magic number: " + this.magicNumber);
		}
		//跳过magic number的那一行
		return lines.subList(1, lines.size());
	}

	/**
	 * Skips over a sequence of blank lines and lines of comments until some image
	 * information is found returning the remaining lines of the file.
	 * 
	 * @param lines the currently unread lines of text of the PPM file
	 * @return the remaining lines of text after reading a sequence of blank lines
	 *         and lines of comments
	 * @throws PpmException if lines is empty
	 */
	private List<String> skipBlanksAndComments(List<String> lines) {
		
		if (lines.isEmpty()) {
			throw new PpmException("unexpected end of file");
		}
		
		//检查每一行,直到找到非空格和非注释的行
		for (int i = 0; i < lines.size(); i++) {
			//删除第i行的首尾空格
			String s = lines.get(i).trim();
			//s不能是空格或者以#开头的字符串(以#号开头的字符串为注释)
			if (!(s.isEmpty() || s.startsWith("#"))) {
				// this line is not blank, return the sublist starting at this line
				return lines.subList(i, lines.size());
			}
		}
		//未找到复合条件的行,则返回空list
		return lines.subList(lines.size(), lines.size());
	}

	/**
	 * Attempts to read the width and height of the image from the file returning
	 * the remaining lines of the file.
	 * 
	 * @param lines the currently unread lines of text of the PPM file
	 * @return the remaining lines of text after reading the image width and height
	 * @throws PpmException          if the width or height is missing
	 * @throws NumberFormatException if a non-numeric width or height is found
	 */
	private List<String> readSize(List<String> lines) {
		if (lines.isEmpty()) {
			throw new PpmException("unexpected end of file");
		}
		// width and height should be on the first line of the sublist
		//删除第一行数据的首尾空格
		String s = lines.get(0).trim();
		//将第一行数据以空格分割成字符串数组
		String[] parts = s.split("\\s+");
		//parts数组只会有两个元素,即width和height。多于两个即为异常
		if (parts.length != 2) {
			throw new PpmException("missing size : " + s);
		}
		//获取图片的宽
		this.width = Integer.parseInt(parts[0]);
		//获取图片的高
		this.height = Integer.parseInt(parts[1]);
		//跳过第一行,将剩余行返回
		return lines.subList(1, lines.size());
	}

	/**
	 * Attempts to read the maximum color value from the file returning the
	 * remaining lines of the file.
	 * 
	 * @param lines the currently unread lines of text of the PPM file
	 * @return the remaining lines of text after reading the maximum color value
	 * @throws PpmException          if the maximum color value is missing
	 * @throws NumberFormatException if a non-numeric maximum color value is found
	 */
	private List<String> readMaxValue(List<String> lines) {
		if (lines.isEmpty()) {
			throw new PpmException("unexpected end of file");
		}
		// max color value should be on the first line of the sublist
		//删除第一行数据的为首空格
		String s = lines.get(0).trim();
		//将第一行数据以空格分割成字符串数组
		String[] parts = s.split("\\s+");
		//parts数组只有一个元素,即max value,否则即为异常
		if (parts.length != 1) {
			throw new PpmException("missing max value : " + s);
		}
		//获取max value,即最大颜色值
		this.maxValue = Integer.parseInt(parts[0]);
		//跳过第一行,将剩余行返回
		return lines.subList(1, lines.size());
	}

	/**
	 * Tests that the specified value is between 0 and the maximum color value.
	 * 
	 * @param val a color value to test
	 * @throws PpmException if the specified value is not between 0 and the maximum
	 *                      color value
	 */
	private void testColorValue(int val) {
		if (val < 0) {
			throw new PpmException("color value less than zero");
		}
		if (val > this.maxValue) {
			throw new PpmException("color value greater than " + this.maxValue);
		}
	}

	/**
	 * Attempts to read the pixel color data from the remaining lines of the file.
	 * This method assumes that there are a multiple of 3 integer values on each
	 * line (i.e., RGB triplets are not split across multiple lines).
	 * 
	 * <p>
	 * The pixels are stored in the SimpleImage object this.img
	 * 
	 * @param lines the currently unread lines of text of the PPM file
	 * @throws PpmException          if a color value less than 0 or greater than
	 *                               the maximum color values is found
	 * @throws NumberFormatException if a non-numeric color value is found
	 */
	private void readPixels(List<String> lines) {
		// IMPLEMENT THIS METHOD
		
		String[] allPiexls = new String[this.img.width()*this.img.height()*3];
		int count = 0;
		for(String s : lines) {
			//将第一行数据以空格分割成字符串数组
			String[] sArr = s.split("\\s+");
			for(int i = 0; i < sArr.length; i++) {
				//空格跳过
				if(sArr[i].trim().isEmpty()) {
					continue;
				}
				//将组成像素的点存入到allPiexls中
				allPiexls[count++] = sArr[i];
			}
		}
		
		//填充piexels
		int index = 0;
		for(int h = 0; h < this.img.height(); h++) {
			for(int w = 0; w < this.img.width(); w++) {
				//每三个点表示一个像素,每个点以“$”分隔
				String p = allPiexls[index] + "$" + allPiexls[index + 1] + "$" + allPiexls[index + 2];
				index+=3;
				// p 类似 0$36$82 
				this.img.piexels[h][w] = p;
			}
		}
	}

	
	/*
	 * EVERYTHING ELSE IS ALREADY DONE
	 */
	
	/**
	 * Returns the width of the image read by this reader.
	 * 
	 * @return the width of the image read by this reader
	 */
	public int width() {
		return this.width;
	}

	/**
	 * Returns the height of the image read by this reader.
	 * 
	 * @return the height of the image read by this reader
	 */
	public int height() {
		return this.height;
	}

	/**
	 * Returns the magic number of the image read by this reader.
	 * 
	 * @return the magic number of the image read by this reader
	 */
	public String magicNumber() {
		return this.magicNumber;
	}

	/**
	 * Returns the maximum color value of the image read by this reader.
	 * 
	 * @return the maximum color value of the image read by this reader
	 */
	public int maxValue() {
		return this.maxValue;
	}

	/**
	 * Returns the image read by this reader.
	 * 
	 * @return the image read by this reader
	 */
	public SimpleImage image() {
		return this.img;
	}

	/**
	 * Reads a small PPM image of the Queen's tricolor flag and displays the image.
	 * 
	 * @param args not used
	 */
	public static void main(String[] args) {

		//String filename = "tricolor.ppm";
		//String dir = "/main/resources/img";

		//文件名
		String filename = "grant_hall.ppm";
		//文件路径
		String dir = "/main/resources/img";
		
		PpmAsciiReader r = new PpmAsciiReader(dir, filename);
		//输出文件中的信息
		System.out.println("header : " + r.magicNumber());
		System.out.println("width  : " + r.width());
		System.out.println("height : " + r.height());
		System.out.println("max    : " + r.maxValue());

		//设置frame标题
		JFrame frame = new JFrame("PpmAsciiReader");
		//设置frame窗口最小大小
		frame.setMinimumSize(new Dimension(500, 100));
		//设置frame布局
		frame.getContentPane().setLayout(new FlowLayout());
		//获得BufferedImage
		BufferedImage buf = r.image().asBufferedImage();
		//向frame中添加图像
		frame.getContentPane().add(new JLabel(new ImageIcon(buf)));
		//根据窗口里面的布局及组件的preferredSize来确定frame的最佳大小
		frame.pack();
		//显示frame
		frame.setVisible(true);
		/**
		 * 点击窗口右上角关闭,四种关闭方式:
		 * 		1.DO_NOTHING_ON_CLOSE,(在你点击关闭按钮的时候,不会被关闭,)不执行任何操作。
		 * 		2.HIDE_ON_CLOSE,(当你点击关闭按钮的时候,不会释放内存,只是隐藏该界面,没有真正的关闭,还占有资源)只隐藏界面,setVisible(false)。
		 * 		3.DISPOSE_ON_CLOSE,点击关闭按钮的时候,隐藏并释放窗体,dispose(),当最后一个窗口被释放后,则程序也随之运行结束。
		 * 		4.EXIT_ON_CLOSE,直接关闭应用程序,System.exit(0)。一个main函数对应一整个程序。
		 */
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
}

PpmException.java:

package com.xj.ppm.toimg;

/**
 * Exception class for Assignment 3.
 *
 */
public class PpmException extends RuntimeException {
	/**
	 * Constant needed by the Serializable interface.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Initialize this exception with the specified message.
	 * 
	 * @param msg the exception message
	 */
	public PpmException(String msg) {
		super(msg);
	}
}

PpmStacker.java:

package com.xj.ppm.toimg;

import java.util.ArrayList;
import java.util.List;

/**
 * A class that uses image stacking to perform noise reduction on images. On
 * initialization, a stacker object reads a series of PPM text-based image
 * files. A noise reduced image is produced by computing the average color of
 * each pixel over all of the images.
 */
public class PpmStacker {

	/**
	 * The noise reduced average image.
	 */
	private SimpleImage avg;

	/**
	 * The stack of images.
	 */
	private List<SimpleImage> stack;

	/**
	 * Initializes an image stacker object by reading a series of PPM text-based
	 * image files and computing a noise reduced image by averaging the pixel colors
	 * over all of the images. The image files are assumed to be all in the same
	 * directory and the names of the image files should follow the pattern
	 * PREFIX_000.ppm, PREFIX_001.ppm, PREFIX_002.ppm, etc., where PREFIX is a
	 * common filename prefix.
	 * 
	 * @param dir        a directory that the image files are contained in
	 * @param filePrefix the common filename prefix
	 * @param n          the number of images in the stack
	 * @throws PpmException if a PPM file in the stack is readable but contains
	 *                      invalid or unexpected data, or if images in the stack
	 *                      have different sizes
	 */
	public PpmStacker(String dir, String filePrefix, int n) {
		if (n < 0) {
			throw new IllegalArgumentException("number of images less than zero");
		}
		//读取dir路径下的n个filePrefix图像文件存储到stack中
		this.stack = readImageStack(dir, filePrefix, n);
		//将stack中所有图像文件的像素平均值合成一个SimpleImage,即avg
		this.avg = average();
	}

	/**
	 * Reads a series of PPM text-based images files and returns the list of read
	 * images.
	 * 
	 * @param dir        a directory that the image files are contained in
	 * @param filePrefix the common filename prefix
	 * @param n          the number of images in the stack
	 * @return a list of images
	 * @throws PpmException if a PPM file in the stack is readable but contains
	 *                      invalid or unexpected data, or if images in the stack
	 *                      have different sizes
	 */
	private static List<SimpleImage> readImageStack(String dir, String filePrefix, int n) {
		
		List<SimpleImage> simpleImages = new ArrayList<>();
		PpmAsciiReader r = null;
		for(int i = 0; i < n; i++) {
			//filename = one_nebula + _00 + i + .ppm
			String filename = filePrefix + "_00" + i + ".ppm";
			//读取dir路径下的filename文件
			r = new PpmAsciiReader(dir, filename);
			//添加到list集合中
			simpleImages.add(r.image());
		}
		
		return simpleImages;
	}

	/**
	 * Computes the average image over all of the images in the image stack
	 * this.stack without modifying any images in this.stack
	 * 
	 * @return the average image
	 */
	private SimpleImage average() {
		//获得图像的高
		int height = this.getImage(0).height();
		//获得图像的宽
		int width = this.getImage(0).width();
		
		//定义一个SimpleImage图像
		SimpleImage s = new SimpleImage(width,height);
		
		//将10个图像的像素平均值填充到SimpleImage
		for(int h =0; h < height; h++) {
			for(int w = 0; w < width; w++) {
				//RGB是red,green, blue的简写,也就是红绿蓝三种颜色。他们是三原色,通过不同的比例相加,以产生多种多样的色光
				int r = 0;
				int g = 0;
				int b = 0;
				//因为10个图像,循环10次
				for(int i = 0; i < 10; i++) {
					// p 类似 0$36$82 
					String p = this.getImage(i).getPiexel()[h][w];
					String[] px = p.split("\\$");
					r+= Integer.parseInt(px[0]);
					g+= Integer.parseInt(px[1]);
					b+= Integer.parseInt(px[2]);
				}
				//重新组装成 0$36$82 格式
				s.piexels[h][w] = r/10 + "$" + g/10 + "$" + b/10;
			}
		}
		return s;
	}
	
	
	/*
	 * EVERYTHING ELSE IS ALREADY DONE
	 */

	/**
	 * Returns the noise reduced averaged image.
	 * 
	 * @return the noise reduced averaged image
	 */
	public SimpleImage getAverage() {
		return this.avg;
	}

	/**
	 * Returns the specified image in the image stack. The first image in the stack
	 * is image 0.
	 * 
	 * @param i the index of the image in the image stack to return
	 * @return the specified image in the image stack
	 */
	public SimpleImage getImage(int i) {
		return this.stack.get(i);
	}
}

SimpleImage.java:

package com.xj.ppm.toimg;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

/**
 * A simple representation of a color 2D image that allows the user to query the
 * dimensions of the image, retrieve an image pixel, and set an image pixel.
 * 
 * <p>
 * An image has a size defined by a positive integer width (in pixels) and positive
 * integer height (in pixels). 
 * 
 * <p>
 * Pixels are retrieved and set using 0-based row and column indexes. The upper-left
 * corner is the origin of the image (row = 0, column = 0).
 *
 */
public class SimpleImage {

	private int width;
	
	private int heigth;
	
	//存储像素点的二维数组
	public String[][] piexels = null;
	
	/**
	 * Initialize an image to the specified width and height. The pixels of the
	 * image are set to {@code Color.BLACK}.
	 * 
	 * @param width  the width of the image
	 * @param height the height of the image
	 * @throws IllegalArgumentException if the width or height of the image is less
	 *                                  than 1
	 */
	public SimpleImage(int width, int height) {

		this.width = width;
		this.heigth = height;
		this.piexels = new String[height][width];
	}

	/**
	 * Initialize an image by copying the specified image. The image has the
	 * same width and height of the copied image. The image has its own grid
	 * of pixels, and each pixel is equal to the corresponding pixel in the
	 * copied image.
	 * 
	 * @param img an image to copy
	 */
	public SimpleImage(SimpleImage img) {
		// IMPLEMENT THIS CONSTRUCTOR
		this.width = img.width;
		this.heigth = img.heigth;
		
		for(int h = 0; h < img.heigth ; h++) {
			for(int w = 0; w < img.width; w++) {
				this.piexels[h][w] = img.getPiexel()[h][w];
			}
		}
		
	}
	
	public int height() {
		return this.heigth;
	}
	
	public int width() {
		return this.width;
	}
	
	public String[][] getPiexel(){
		return this.piexels;
	}
	
	
	public Color get(int height, int width) {
		String[][] tempP = this.getPiexel();
		String p = tempP[height][width];
		if(p == null || "".equals(p)) {
			throw new PpmException("is empty...");
		}
		// p 类似 0$36$82 
		String[] px = p.split("\\$");
		return new Color(Integer.parseInt(px[0]),Integer.parseInt(px[1]),Integer.parseInt(px[2]));
	}
	
	
	

	/**
	 * IMPLEMENTED FOR YOU.
	 * 
	 * <p>
	 * Returns a {@code BufferedImage} having equal pixels as this image. This
	 * method can be used to provide a suitable image for the Java Standard
	 * Library classes that are part of the Abstract Window Toolkit.
	 * 
	 * @return a BufferedImage having equal pixels as this image
	 */
	public BufferedImage asBufferedImage() {
		//定义一个BufferedImage
		BufferedImage b = new BufferedImage(this.width(), this.height(), BufferedImage.TYPE_INT_ARGB);
		//向BufferedImage中填充数据
		for (int i = 0; i < this.height(); i++) {
			for (int j = 0; j < this.width(); j++) {
				b.setRGB(j, i, this.get(i, j).getRGB());
			}
		}
		return b;
	}
	
	/**
	 * Creates and displays a SimpleImage.
	 * 
	 * @param args not used
	 */
	public static void main(String[] args) {
		SimpleImage img = new SimpleImage(100, 50);
		//文件名
		String filename = "grant_hall.ppm";
		//文件路径
		String dir = "/main/resources/img";
		//读取文件
		PpmAsciiReader r = new PpmAsciiReader(dir, filename);
		img = r.image();
		//设置frame标题
		JFrame frame = new JFrame("SimpleImage");
		//设置frame窗口最小大小
		frame.setMinimumSize(new Dimension(500, 100));
		//设置frame布局
		frame.getContentPane().setLayout(new FlowLayout());
		//获得BufferedImage
		BufferedImage buf = img.asBufferedImage();
		//向frame中添加图像
		frame.getContentPane().add(new JLabel(new ImageIcon(buf)));
		//根据窗口里面的布局及组件的preferredSize来确定frame的最佳大小
		frame.pack();
		//显示frame
		frame.setVisible(true);
		/**
		 * 点击窗口右上角关闭,四种关闭方式:
		 * 		1.DO_NOTHING_ON_CLOSE,(在你点击关闭按钮的时候,不会被关闭,)不执行任何操作。
		 * 		2.HIDE_ON_CLOSE,(当你点击关闭按钮的时候,不会释放内存,只是隐藏该界面,没有真正的关闭,还占有资源)只隐藏界面,setVisible(false)。
		 * 		3.DISPOSE_ON_CLOSE,点击关闭按钮的时候,隐藏并释放窗体,dispose(),当最后一个窗口被释放后,则程序也随之运行结束。
		 * 		4.EXIT_ON_CLOSE,直接关闭应用程序,System.exit(0)。一个main函数对应一整个程序。
		 */
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
}

NoiseProcessor.java:

package com.xj.ppm.toppm;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;

/**
 * @Author : xjfu
 * @Date : 2023/2/22 20:36
 * @Description : 图像降噪处理程序
 */
public class NoiseProcessor {

	private File source;
	private File destDir;

	public NoiseProcessor(File source, File destDir) {
		this.source = source;
		this.destDir = destDir;
	}

	public void process(int numNoiseyImages) throws IOException {
		//获取带后缀的文件名
		String filename = source.getName();
		//将文件名中的后缀截掉(例如".jpb"、".gif"等)
		filename = filename.substring(0, filename.length() - 4);

		BufferedImage image = ImageIO.read(source);

		//获取文件的高
		int height = image.getHeight();
		//获取文件的宽
		int width = image.getWidth();
		
		//定义一个长度为10的字符类型的输出流
		PrintWriter[] printers = new PrintWriter[numNoiseyImages];
		//将文件头信息写入到printers[i]中
		for (int i = 0; i < numNoiseyImages; i++) {
			/**
			 * %s:输出字符串
			 * %3d:输出三位的数字
			 * %03d:输出三位的数字,不足用0补全
			 */
			//拼出ppm类型文件的文件名
			String numFilename = String.format("%s_%03d.ppm", filename, i);
			//在destDir路径下创建名为numFilename的文件
			File destFile = new File(destDir, numFilename);
			printers[i] = new PrintWriter(destFile);
			//第一行写入“P3”
			printers[i].println("P3");
			//第二行写入“width height”,即图像的宽和高
			printers[i].println(width + " " + height);
			//第三行写入“255”,即maxValue
			printers[i].println("255");
		}
		
		//将经过处理的像素信息写入到printers[i]中
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				//获取图像image坐标为(x,y)的像素
				Color pixel = new Color(image.getRGB(x, y));
				//将像素pixel根据特定算法分成numNoiseyImages份
				Color[] pixels = getNoise(pixel, numNoiseyImages);
				//写入到printers[i]文件中
				for (int i = 0; i < numNoiseyImages; i++) {
					printers[i].println(
							pixels[i].getRed() + " " + pixels[i].getGreen()
							+ " " + pixels[i].getBlue());
				}
			}
		}
		
		//关闭流
		for (PrintWriter p : printers) {
			p.close();
		}
	}

	//将像素pixel根据特定算法分成num份
	private Color[] getNoise(Color pixel, int num) {
		//像素中的R值部分
		List<Integer> red = getIntNoise(pixel.getRed(), num);
		//像素中的G值部分
		List<Integer> green = getIntNoise(pixel.getGreen(), num);
		//像素中的B值部分
		List<Integer> blue = getIntNoise(pixel.getBlue(), num);
		Color[] colors = new Color[num];
		
		for (int i = 0; i < num; i++) {
			//通过R、G、B重新组合成一个像素,即RGB
			colors[i] = new Color(red.get(i), green.get(i), blue.get(i));
		}
		return colors;
	}

	//将val根据特定算法分成num份
	private List<Integer> getIntNoise(int val, int num) {
		List<Integer> values = new ArrayList<Integer>();
		//获取255-val的绝对值
		int upDiff = Math.abs(255-val);
		int downDiff = val;
		int diff;
		if (downDiff < upDiff) {
			diff = downDiff;
		} else {
			diff = upDiff;
		}
		for (int i = 0; i < num; i++) {
			if (i%2 == 0) {
				//偶数减法
				values.add(val - diff);
			} else {
				//奇数加法
				values.add(val + diff);
			}
		}
		//将values中的元素进行随机排列
		Collections.shuffle(values);
		
		return values;
	}


}

StackCreator.java:

package com.xj.ppm.toppm;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
 * @Author : xjfu
 * @Date : 2023/2/22 20:36
 * @Description : 图像降噪处理程序窗口
 */
public class StackCreator {

	public static final int NUM_NOISEY_IMAGES = 10;
	
	private File source;
	private File destRoot;

	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		StackCreator converter = new StackCreator();
		Chooser chooser = converter.new Chooser();
		chooser.setVisible(true);
	}

	private void processFiles() throws IOException {
		//图片文件数组
		File[] originalImages = source.listFiles(new FilenameFilter() {

			public boolean accept(File dir, String name) {
				//图片的名字全部转成小写
				String tmp = name.toLowerCase();
				//判断图片的后缀
				return tmp.endsWith("jpg") || tmp.endsWith("gif")
						|| tmp.endsWith("bmp") || tmp.endsWith("png");
			}
		});

		for (File original : originalImages) {
			//获取带后缀的文件名
			String filename = original.getName();
			//将文件名中的后缀截掉(例如".jpb"、".gif"等)
			filename = filename.substring(0, filename.length() - 4);

			File destDir = new File(destRoot, filename);
			//判断destDir是否存在,否则创建
			if (!destDir.exists()) {
				//创建filename名字的文件夹
				destDir.mkdir();
			}
			System.out.println("Processing " + destDir.getCanonicalPath());

			try {
				NoiseProcessor creator = new NoiseProcessor(original, destDir);
				//开始将source文件分成NUM_NOISEY_IMAGES份写入到指定目录下
				creator.process(NUM_NOISEY_IMAGES);
			} catch (IOException e) {
				System.err.println("Error processing " + original);
				e.printStackTrace(System.err);
			}
		}

		JOptionPane.showMessageDialog(null, "Done.");

	}

  class Chooser extends JFrame implements ActionListener {
		private static final long serialVersionUID = -8389660714995462075L;

		private JTextField sourceDirTF;
		private JTextField destDirTF;
		private JButton sourceButton;
		private JButton destButton;

		public Chooser() throws IOException {
			//定义一个面板,要求是网格布局
			JPanel panel = new JPanel(new GridBagLayout());
			GridBagConstraints constraints = new GridBagConstraints();

			//布局为水平排列
			constraints.fill = GridBagConstraints.VERTICAL;
			constraints.gridx = 0;
			constraints.gridy = 0;
			//在(0,0)的坐标位置上添加名为Source的Label
			panel.add(new JLabel("Source: "), constraints);

			source = new File(".");
			//声明了一个JTextField
			sourceDirTF = new JTextField();
			//将source的抽象路径设置为JTextFileld中
			sourceDirTF.setText(source.getCanonicalPath());
			constraints.gridx++;
			//在(1,0)的坐标位置上添加了sourceDirTF
			panel.add(sourceDirTF, constraints);

			//声明了一个名为Browse的按钮
			sourceButton = new JButton("Browse");
			//添加一个事件监听器
			sourceButton.addActionListener(this);
			constraints.gridx++;
			//在(2,0)的坐标位置添加一个按钮
			panel.add(sourceButton, constraints);

			// destination
			constraints.gridx = 0;
			constraints.gridy++;
			//在(0,1)的位置添加一个名为Destination的label
			panel.add(new JLabel("Destination: "), constraints);

			destRoot = new File(".");
			//声明一个JTextField
			destDirTF = new JTextField();
			//将destRoot的抽象路径设置为JTextFileld中
			destDirTF.setText(destRoot.getCanonicalPath());
			constraints.gridx++;
			//在(1,1)的位置添加一个destDirTF
			panel.add(destDirTF, constraints);

			//声明了一个名为Browse的按钮
			destButton = new JButton("Browse");
			//添加监听事件
			destButton.addActionListener(this);
			constraints.gridx++;
			//(2,1)的坐标位置上添加了一个按钮
			panel.add(destButton, constraints);

			//声明一个名为Process的按钮
			JButton processButton = new JButton("Process");
			//以匿名内部类的方式添加一个监听事件
			processButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent arg0) {
					
					try {
						//处理文件的方法
						processFiles();
					} catch (IOException e) {
						e.printStackTrace();
					}
					
				}
			});
			constraints.gridx = 0;
			constraints.gridy++;
			//(0,2)的坐标位置添加一个按钮
			panel.add(processButton, constraints);
			//将panel添加到JFrame中
			add(panel);
			//设置标题
			setTitle("图像转ppm文件");
			//JFrame窗口大小随组件进行自适应
			pack();
			//用户单击窗口的关闭按钮时执行此句
			setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			//当创建多个窗口且位置相同时可以重叠
			setLocationByPlatform(true);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			File current;
			String title;
			//判断那个按钮被点击
			if (e.getSource() == sourceButton) {
				current = source;
				title = "Choose source folder";
			} else {
				current = destRoot;
				title = "Choose destination folder";
			}

			//定义一个文件导航窗口
			JFileChooser chooser = new JFileChooser();
			//设置导航窗口的标题
			chooser.setDialogTitle(title);
			//将current设置为默认路径
			chooser.setCurrentDirectory(current);
			//只能选择文件夹
			chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
			chooser.setAcceptAllFileFilterUsed(false);

			//如果点击“打开”,则返回0;如果点击“取消”则返回1
			int result = chooser.showOpenDialog(this);
			if (result == JFileChooser.APPROVE_OPTION) {
				File dir = chooser.getSelectedFile();
				try {
					//判断那个按钮被点击
					if (e.getSource() == sourceButton) {
						source = dir;
						//将选择的路径赋值给sourceDirTF
						sourceDirTF.setText(source.getCanonicalPath());
					} else {
						destRoot = dir;
						//将选择的路径赋值给destDirTF
						destDirTF.setText(destRoot.getCanonicalPath());
					}
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
		}
	}
}


grant_hall.ppm:

 

tricolor.ppm:

P3
# width height
9 6
# max color value
255
# pixel colors, each triplet is a pixel color red green blue
  0  36  82     0  36  82     0  36  82
250 189  15   250 189  15   250 189  15
185  14  49   185  14  49   185  14  49
  0  36  82     0  36  82     0  36  82
250 189  15   250 189  15   250 189  15
185  14  49   185  14  49   185  14  49
  0  36  82     0  36  82     0  36  82
250 189  15   250 189  15   250 189  15
185  14  49   185  14  49   185  14  49
  0  36  82     0  36  82     0  36  82
250 189  15   250 189  15   250 189  15
185  14  49   185  14  49   185  14  49
  0  36  82     0  36  82     0  36  82
250 189  15   250 189  15   250 189  15
185  14  49   185  14  49   185  14  49
  0  36  82     0  36  82     0  36  82
250 189  15   250 189  15   250 189  15
185  14  49   185  14  49   185  14  49

wallpaper.jpg:

 三、运行结果

1.运行PpmAsciiReader中的main方法,将tricolor.ppm文件转成图片

 2.运行PpmAsciiReader中的main方法,将grant_hall.ppm文件转成图片

3.运行StackCreator中的main方法,将wallpaper.jpg文件转成10个ppm文件

4.运行Denoise中的main方法,将wallpaper文件夹下的10个ppm文件还原成一个jpg图片

 说明:Denoise类就负责图片降噪,即通过特定算法提高图片的清晰度。

四、参考

1.PPM文件格式详解_kinghzkingkkk的博客-CSDN博客_ppm支持多少位

2.PPM文件格式_gengshenghong的博客-CSDN博客_ppm文件 

3.PPM文件的正确打开方式_Iking123的博客-CSDN博客_ppm文件怎么打开 

4.IntelliJ IDEA使用第三方应用程序打开项目中的某类型文件_棒棒不是糖_的博客-CSDN博客_idea 打开.ppm文件 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值