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.