[BMP format function analyzer] java language mathematical analysis project

Keywords: Java image processing

1, Project introduction:

  let's first show you the effect of this small project:

  pic.bmp image generated after running

  yes, the main function of this project is to generate corresponding images through functions.

  the main technologies used are: IO stream, BMP format analysis and object-oriented programming.

  the project structure is as follows:

   the Main class is mainly used for testing. I will introduce the remaining three classes one by one below.

2, Project content:

① Color class

   as we all know, a picture is actually a two-dimensional matrix composed of pixels, so we must build a Color class to represent pixels, and the storage of picture information is a Color type two-dimensional array storage.

package www.spd.pic;

public class Color {

	public static final Color BLACK = new Color((byte)0x00, (byte)0x00, (byte)0x00);
	public static final Color RED = new Color((byte)0xff, (byte)0x00, (byte)0x00);
	public static final Color GREEN = new Color((byte)0x00, (byte)0xff, (byte)0x00);
	public static final Color BLUE = new Color((byte)0x00, (byte)0x00, (byte)0xff);


	byte red;
	byte green;
	byte blue;

	/**
	 * The three parameters are the values corresponding to the three primary colors
	 * @param red gules
	 * @param green green
	 * @param blue blue
	 */
	public Color(byte red, byte green, byte blue) {

		this.red = red;
		this.green = green;
		this.blue = blue;

	}

	/**
	 * Inherit the {@ link Object#toString()} method to facilitate output
	 */
	@Override
	public String toString() {
		int r = red >= 0 ? red : (red + 0x100);
		int g = green >= 0 ? green : (green + 0x100);
		int b = blue >= 0 ? blue : (blue + 0x100);
		return String.format("#%02x%02x%02x", r, g, b);
	}

	/**
	 * Converts color information into a byte array
	 * @return
	 */
	public byte[] toByteArray() {
		byte[] arr = new byte[3];
		arr[0] = blue;
		arr[1] = green;
		arr[2] = red;
		return arr;
	}

}

② BMP class:

   for the analysis of BMP file format, see this blog, which is written in detail. My project refers to this blog: Bitmap (bmp) file format analysis.

package www.spd.pic;

import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BMP {

	//file name
	String name;
	//Size of the entire BMP file
	int bfSize;
	//The width of the bitmap, in pixels
	int biWidth;
	//The height of the bitmap, in pixels
	int biHeight;
	//Bytes occupied by all pixels of bitmap, BI_RGB can be set to 0
	int biSizeImage;
	//Because the 32-bit Windows operating system processes 4 bytes (32 bits) faster, the number of bytes occupied by each line of BMP color is specified as an integer multiple of 4.
	//If there are two pixels in a line of color, it occupies a total of 6 bytes. If you want to supplement 4 * 2 = 8 bytes, you need to add two 0 bytes.
	//After calculation, appendbit = biwidth% 4;
	int appendBytes;
	//Indicates how many bytes a row of pixels really occupy, that is, the bytes to be occupied by itself plus the complement bytes
	int widthBytes;

	//Pixel information of picture
	Color[][] pixelsMatrix;

	public BMP(String path) {
		this(new File(path));
	}

	/**
	 * Obtain various information of the picture (i.e. all member variables) by passing the parameter {@ link java.io.File}
	 * @param file BMP file
	 */
	public BMP(File file) {

		/* Get the file name and use regular expression to judge whether it is a standard bmp image file through the suffix */
		name = file.getName();
		Pattern p = Pattern.compile(".*\\.bmp");
		Matcher m = p.matcher(name);
		if (!m.find()) throw new IllegalArgumentException("Incorrect file suffix!!");

		/* Get byte array */
		byte[] arr = getByteArray(file);

		if (arr == null) throw new IllegalArgumentException("The file is empty!!");

		/* bmp The first two bytes of the file are "bm", i.e. 0x4d42, used for identification. If the first two bits are not this byte, an exception will be thrown */
		int bfType = byteArrayToInteger(arr, 1, 0);
		if (bfType != 0x4d42) throw new IllegalArgumentException("The file is damaged!!");

		bfSize = byteArrayToInteger(arr, 0x5, 0x2);
		biWidth = byteArrayToInteger(arr, 0x15, 0x12);
		biHeight = byteArrayToInteger(arr, 0x19, 0x16);
		
		/* 1c To 1f bits represent the number of bits a pixel occupies, and 24 bits are corresponding to the commonly used hexadecimal color representation. The two bits are black-and-white pictures */
		int biBitCount = byteArrayToInteger(arr, 0x1f, 0x1c);
		if (biBitCount != 24) throw new IllegalArgumentException("The picture is not a 24 bit true color bitmap!!");

		biSizeImage = byteArrayToInteger(arr, 0x27, 0x24);
		appendBytes = biWidth % 4;
		widthBytes = biWidth * 3 + appendBytes;

		pixelsMatrix = new Color[biHeight][biWidth];

		/* Double layer for loop to obtain pixel information */
		for (int i = 0; i < biHeight; i++) {

			int pixelEnd = (biHeight - i - 1) * widthBytes + 0x36;

			for (int j = 0; j < biWidth; j++) {

				pixelsMatrix[i][j] = new Color(arr[pixelEnd + 2], arr[pixelEnd + 1], arr[pixelEnd]);
				pixelEnd += 3;

			}

		}

	}

	/**
	 * Get a picture of a certain color
	 * @param color If Chuan Shen is red, it is a picture with only red. and so on
	 * @param name file name
	 * @param width image width
	 * @param height Picture height
	 */
	public BMP(Color color, String name, int width, int height) {

		this.name = name;
		biWidth = width;
		biHeight = height;
		appendBytes = biWidth % 4;
		widthBytes = biWidth * 3 + appendBytes;
		pixelsMatrix = new Color[biHeight][biWidth];

		/* Traverse the matrix and pass the color parameter to each pixel */
		for (int i = 0; i < biHeight; i++) {
			for (int j = 0; j < biWidth; j++) {
				pixelsMatrix[i][j] = color;
			}
		}

		biSizeImage = widthBytes * height;
		bfSize = biSizeImage + 0x36;

	}

	public BMP(String name, int width, int height) {
		this(new Color((byte)0xff, (byte)0xff, (byte)0xff), name, width, height);
	}

	public BMP(Color color, int width, int height) {
		this(color, "pic.bmp", width, height);
	}

	public BMP(int width, int height) {
		this("pic.bmp", width, height);
	}

	/**
	 * Inherit {@ link Object#toString()} to print picture information
	 */
	@Override
	public String toString() {
		return "BMP{" +
				"\n\tname=\"" + name +
				"\", \n\tbfSize=" + bfSize +
				", \n\tbiWidth=" + biWidth +
				", \n\tbiHeight=" + biHeight +
				", \n\tbiSizeImage=" + biSizeImage +
				", \n\tappendBytes=" + appendBytes +
				", \n\twidthBytes=" + widthBytes +
				"\n}";
	}

	/**
	 * Obtain its byte stream by passing the parameter {@ link java.io.File} and using the buffer stream {@ link java.io.BufferedInputStream}
	 * @param file bmp file
	 * @return Byte stream of picture file
	 */
	private static byte[] getByteArray(File file) {

		try(BufferedInputStream bis =
					new BufferedInputStream(
							new FileInputStream(file));
			ByteArrayOutputStream baos =
					new ByteArrayOutputStream()) {

			int len = -1;
			byte[] flush = new byte[1024];

			while ((len = bis.read(flush)) != -1) {
				baos.write(flush, 0, len);
				baos.flush();
			}

			return baos.toByteArray();

		} catch (FileNotFoundException e) {

			System.err.println("File does not exist!!");
			return null;

		} catch (IOException e) {

			System.err.println("IO Operation exception!!");
			e.printStackTrace();
			return null;

		}

	}

	/**
	 * This method is opposite to {@ link #integerToByteArray(int, int)} < br / >
	 * Get the integer number corresponding to some bits of the byte array < br / >
	 * It is called in middle {@link #BMP(File)}.
	 * @param arr Byte array
	 * @param begin Start indexing
	 * @param end End index
	 * @return Corresponding integer
	 */
	private int byteArrayToInteger(byte[] arr, int begin, int end) {

		int ans = 0;
		int temp;

		if (begin <= end) {
			for (int i = begin; i <= end; i++) {
				ans *= 0x100;
				temp = arr[i] >= 0 ? arr[i] : (arr[i] + 128);
				ans += temp;
			}
		}

		if (begin > end) {
			for (int i = begin; i >= end; i--) {
				ans *= 0x100;
				temp = arr[i] >= 0 ? arr[i] : (arr[i] + 128);
				ans += temp;
			}
		}

		return ans;

	}

	/**
	 * This method is opposite to {@ link #byteArrayToInteger(byte[], int, int)} < br / >
	 * Gets the byte array corresponding to the integer
	 * Important is invoked in {@link #toByteArray()}.
	 * @param size How many bytes does the returned array occupy
	 * @param num The value of an integer
	 * @return Returned byte array
	 */
	private byte[] integerToByteArray(int size, int num) {
		byte[] arr = new byte[size];
		int i = 0;
		while (num > 0) {
			byte temp = (byte) (num % 0x100);
			num /= 0x100;
			try {
				arr[i] = temp;
			} catch (IndexOutOfBoundsException e) {
				throw new IllegalArgumentException("The numeric value is greater than 2^"+size);
			}
			i++;
		}
		return arr;
	}

	/**
	 * Convert the whole picture file into a byte stream
	 * @return Byte stream corresponding to picture file
	 */
	public byte[] toByteArray() {
		try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

			baos.write("BM".getBytes());
			baos.write(integerToByteArray(4, bfSize));
			baos.write(new byte[4]);
			baos.write(integerToByteArray(4, 0x36));
			baos.flush();

			baos.write(integerToByteArray(4, 40));
			baos.write(integerToByteArray(4, biWidth));
			baos.write(integerToByteArray(4, biHeight));
			baos.write(integerToByteArray(2, 1));
			baos.write(integerToByteArray(2, 24));
			baos.write(new byte[4]);
			baos.write(integerToByteArray(4, biSizeImage));
			baos.write(new byte[16]);
			baos.flush();

			byte[] append = new byte[appendBytes];

			for (int i = biHeight - 1; i >= 0 ; i--) {

				for (int j = 0; j < biWidth; j++) {
					baos.write(pixelsMatrix[i][j].toByteArray());
				}

				baos.write(append);
				baos.flush();

			}

			return baos.toByteArray();

		} catch (IOException e) {

			System.err.println("IO Operation exception!!");
			e.printStackTrace();
			return null;

		}
	}

	/**
	 * Write the byte stream of the picture file to the external file through the output stream {@ link java.io.FileOutputStream}
	 */
	public void createFile() {

		byte[] arr = toByteArray();

		try (FileOutputStream fos = new FileOutputStream(name)) {
			fos.write(arr);
			fos.flush();
		} catch (IOException e) {
			System.err.println("Error writing image object as image IO Exception!!");
		}

	}

}

③ getPicByFunc class:

   in the literal sense, "get image class from Function", there is an internal interface functional below it, which can be used as a Function, an internal class Function, in which there is a functional object and a Color object, representing a Function with Color.

package www.spd.pic;

public class getPicByFunc {

	public static BMP getPic(Function func, int size) {
		return getPic("pic.bmp", func, size);
	}

	public static BMP getPic(String name, Function func, int size) {
		return getPic(name, new Function[]{func}, size);
	}

	public static BMP getPic(Function[] functions, int size) {
		return getPic("pic.bmp", functions, size);
	}

	/**
	 * 
	 * @param name Write out the file name
	 * @param functions Several functions
	 * @param size Picture size
	 * @return Return picture
	 */
	public static BMP getPic(String name, Function[] functions, int size) {

		BMP pic = new BMP(name, size * 40, size * 40);
		
		/* Draw axes and scales */
		for (int i = 0; i < size * 40; i++) {

			pic.pixelsMatrix[i][size * 20] = Color.BLACK;
			pic.pixelsMatrix[i][size * 20 - 1] = Color.BLACK;
			pic.pixelsMatrix[size * 20][i] = Color.BLACK;
			pic.pixelsMatrix[size * 20 - 1][i] = Color.BLACK;

			if (i % 20 == 0) {

				pic.pixelsMatrix[i][size * 20 + 1] = Color.BLACK;
				pic.pixelsMatrix[i][size * 20 + 2] = Color.BLACK;
				pic.pixelsMatrix[i][size * 20 - 2] = Color.BLACK;
				pic.pixelsMatrix[i][size * 20 - 3] = Color.BLACK;

				pic.pixelsMatrix[size * 20 + 1][i] = Color.BLACK;
				pic.pixelsMatrix[size * 20 + 2][i] = Color.BLACK;
				pic.pixelsMatrix[size * 20 - 2][i] = Color.BLACK;
				pic.pixelsMatrix[size * 20 - 3][i] = Color.BLACK;

			}

		}

		/* Draw function */
		for (Function func : functions) {

			for (int i = 0; i < size * 40; i++) {
				for (int j = 0; j < size * 40; j++) {
					double x = i / 20.0f - size;
					double y = j /20.0f - size;
					if (func.func.func(x, y)) {
						pic.pixelsMatrix[size * 40 - 1 - j][i] = func.color;
					}
				}
			}

		}

		return pic;

	}

	public interface Functial {
		boolean func(double x, double y);
	}

	public static boolean equal(double a, double b) {
		double n = 0.05;
		return -n < (a - b) && (a - b) < n;
	}

	public static class Function {
		
		Functial func;
		Color color;

		public Function(Functial func, Color color) {
			this.func = func;
			this.color = color;
		}

		public Function(Functial func) {
			this.func = func;
			this.color = Color.BLACK;
		}
	}
	
}



  even if the project is completed here, let's test it.

3, Project testing

   write a Main function:

package www.spd;

import www.spd.pic.Color;

import static www.spd.pic.getPicByFunc.*;
import static java.lang.Math.*;

public class Main {

	@SuppressWarnings("all")
	public static void main(String[] args) {

		double tau = 0.3;
		int a = 4;

		getPic(new Function[]{
				new Function((x, y) -> equal(pow(x, 2) + pow(y, 2) + a * y, a * sqrt(pow(x, 2) + pow(y, 2))), Color.RED),
				new Function((x, y) -> equal(y, (1 / tau * sqrt(2 * PI)) * exp(-pow(x, 2) / 2 * pow(tau, 2))), Color.GREEN),
				new Function(new Circle(3, 3, 3), Color.BLUE),
				new Function(new Circle(-2, -2, 2), new Color((byte)0x66, (byte)0xcc, (byte)0xff))
				}, 20).createFile();

	}

	public static class Circle implements Functial {

		public final int x;
		public final int y;
		public final int radius;

		@Override
		public boolean func(double x, double y) {
			return equal(pow(x-this.x, 2) + pow(y-this.y, 2), pow(radius, 2));
		}

		public Circle(int x, int y, int radius) {
			this.x = x;
			this.y = y;
			this.radius = radius;
		}
	}

}

  test run results:

   there is a watermark under this bmp format picture. I don't know how to go. If I want to go again, I can try to use my project to read pixels in a double-layer for loop. If it is found that there are pixels that are not #ffffff, nor #0000ff, #00ff00, #ff0000, #66ccff, I can assign it as #ffffff, and then the watermark can be removed.

4, Disadvantages of the project:

  let's try to draw a parabola with my project:

  obviously, it has become a breakpoint. It's easy to reason out mathematically.

  my project generates bitmap instead of vector graph, which will naturally occur. The solution is to generate vector graph, but that will be meaningless.

Posted by FFEMTcJ on Thu, 14 Oct 2021 20:37:36 -0700