diff --git a/Fractal.jar b/Fractal.jar new file mode 100644 index 0000000..18681fc Binary files /dev/null and b/Fractal.jar differ diff --git a/example/Cantor.java b/example/Cantor.java new file mode 100644 index 0000000..c88b712 --- /dev/null +++ b/example/Cantor.java @@ -0,0 +1,52 @@ +package example; + +import java.awt.Color; + +import fractal.Fractal; +import fractal.Utilities; + +public class Cantor { + + public static void main(String[] args) { + + Fractal Cantor=new Fractal(1,Utilities::Cantor); + Cantor.setMaxDepth(9); + Cantor.setSampling(100); + + System.out.println("printing Cantor Set ...\n\n"); + + double test=3*3*3*3*3-1; + for(int j=0;j<6;j++) { + for(int i=0;i<=test;i++) + System.out.print((Cantor.checkPix((double)i/test)>=j)?"1":"_"); + System.out.println(""); + } + + System.out.println("\ngenerating Picture ...\n\n"); + + //pre cmpile of the function for the benchmark + Cantor.drawFractal(3*3*3*3*3*3-1, 250, Color.WHITE, Color.BLACK, 5, new double[] {0d}, new double[] {1d}, new double[] {0d}); + Cantor.drawFractalSampling(3*3*3*3*3*3-1, 250, Color.WHITE, Color.BLACK, 5, new double[] {0d}, new double[] {1d}, new double[] {0d}); + + long benchmarkStart=System.nanoTime(); + Utilities.saveImage(Cantor.drawFractal(3*3*3*3*3*3-1, 200, Color.WHITE, Color.BLACK, 5, new double[] {0d}, new double[] {1d}, new double[] {0d}) + ,"Cantor_well_choosen_size_.png","png"); + long perf=System.nanoTime()-benchmarkStart; + System.out.println("performance : "+perf/1e6+" ms\n"); + + + benchmarkStart=System.nanoTime(); + Utilities.saveImage(Cantor.drawFractal(1000, 200, Color.WHITE, Color.BLACK, 5, new double[] {0d}, new double[] {1d}, new double[] {0d}) + ,"Cantor_unprecise_.png","png"); + perf=System.nanoTime()-benchmarkStart; + System.out.println("performance : "+perf/1e6+" ms\n"); + + benchmarkStart=System.nanoTime(); + Utilities.saveImage(Cantor.drawFractalSampling(1000, 200, Color.WHITE, Color.BLACK, 5, new double[] {0d}, new double[] {1d}, new double[] {0d}) + ,"Cantor_precise_.png","png"); + perf=System.nanoTime()-benchmarkStart; + System.out.println("performance : "+perf/1e6+" ms\n"); + + } + +} diff --git a/example/Menger.java b/example/Menger.java new file mode 100644 index 0000000..440e084 --- /dev/null +++ b/example/Menger.java @@ -0,0 +1,50 @@ +package example; + +import java.awt.Color; + +import fractal.Fractal; +import fractal.Utilities; + +public class Menger { + + public static void main(String[] args) { + + Fractal Menger=new Fractal(2,Utilities::Menger); + Menger.setMaxDepth(9); + Menger.setSampling(100); + + System.out.println("printing Menger Fractal ...\n\n"); + double test=3*3*3*3-1; + for(int j=0;j<=test;j++) { + for(int i=0;i<=test;i++) + System.out.print((Menger.checkPix((double)i/test,(double)j/test)>=4)?"O":" "); + System.out.println(""); + } + + + System.out.println("\ngenerating Picture ...\n\n"); + + //pre compile of the function for the benchmark + Menger.drawFractal(3*3*3*3*3*3-1, 3*3*3*3*3*3-1, Color.WHITE, Color.BLACK, 5); + Menger.drawFractalSampling(3*3*3*3*3*3-1, 3*3*3*3*3*3-1, Color.WHITE, Color.BLACK, 5); + + long benchmarkStart=System.nanoTime(); + Utilities.saveImage(Menger.drawFractal(3*3*3*3*3*3-1, 3*3*3*3*3*3-1, Color.WHITE, Color.BLACK, 5) + ,"Menger_well_choosen_size_.png","png"); + long perf=System.nanoTime()-benchmarkStart; + System.out.println("performance : "+perf/1e6+" ms\n"); + + benchmarkStart=System.nanoTime(); + Utilities.saveImage(Menger.drawFractal(1000, 1000, Color.WHITE, Color.BLACK, 5) + ,"Menger_unprecise_.png","png"); + perf=System.nanoTime()-benchmarkStart; + System.out.println("performance : "+perf/1e6+" ms\n"); + + benchmarkStart=System.nanoTime(); + Utilities.saveImage(Menger.drawFractalSampling(1000, 1000, Color.WHITE, Color.BLACK, 5) + ,"Menger_precise_.png","png"); + perf=System.nanoTime()-benchmarkStart; + System.out.println("performance : "+perf/1e6+" ms\n"); + } + +} diff --git a/example/Sierpinsky.java b/example/Sierpinsky.java new file mode 100644 index 0000000..c610813 --- /dev/null +++ b/example/Sierpinsky.java @@ -0,0 +1,65 @@ +package example; + +import java.awt.Color; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import fractal.Fractal; +import fractal.Utilities; + +public class Sierpinsky { + + + + public static final int THREAD=6; + + public static final boolean OVERWRITE=false; + + public static void main(String[] args) { + + + String folderPath = "./sierpinskyAnim"; + if(!(Files.exists(Paths.get(folderPath)) && Files.isDirectory(Paths.get(folderPath)))) { + try { + Files.createDirectories(Paths.get(folderPath)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + Fractal sierpinsky =new Fractal(2,Utilities::Sierpinski); + sierpinsky.setSampling(80); + sierpinsky.setMaxDepth(10); + + Utilities.saveImage(sierpinsky.drawFractalSampling(1000, 1000,Color.WHITE, Color.BLACK,2.0, 8,new double[] {-1.0,0.0} + ,new double[]{2.0,0},new double[]{0,2.0}), + "sierpinsky_precise_"+".png","png"); + + ExecutorService executor = Executors.newFixedThreadPool(THREAD); + int frames=1000; + for(int i=0;i { + System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName()); + double zoomLvl=Math.exp(-taskId/721.35)*2; + double bright=(1.25-(double)(taskId%500)/500d*1.0/4.0)*1.5d; + Utilities.saveImage(sierpinsky.drawFractalSampling(1000, 1000,Color.WHITE, Color.BLACK,bright, 8+((taskId>=500)?1:0),new double[] {-1+taskId/4000d,taskId/2000d} + ,new double[]{zoomLvl,0},new double[]{0,zoomLvl}), + folderPath+"/output"+String.format("%03d", taskId)+".png","png"); + }); + } + executor.shutdown(); + + try { + executor.awaitTermination((long) 1e10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/images/Menger_precise_.png b/images/Menger_precise_.png new file mode 100644 index 0000000..d3d325b Binary files /dev/null and b/images/Menger_precise_.png differ diff --git a/images/Menger_precise_zoom.png b/images/Menger_precise_zoom.png new file mode 100644 index 0000000..413612f Binary files /dev/null and b/images/Menger_precise_zoom.png differ diff --git a/images/Menger_unprecise_.png b/images/Menger_unprecise_.png new file mode 100644 index 0000000..b070737 Binary files /dev/null and b/images/Menger_unprecise_.png differ diff --git a/images/Menger_unprecise_zoom.png b/images/Menger_unprecise_zoom.png new file mode 100644 index 0000000..720afb9 Binary files /dev/null and b/images/Menger_unprecise_zoom.png differ diff --git a/images/schemaPix.png b/images/schemaPix.png new file mode 100644 index 0000000..d70a146 Binary files /dev/null and b/images/schemaPix.png differ diff --git a/javaDocs.zip b/javaDocs.zip new file mode 100644 index 0000000..adebfc1 Binary files /dev/null and b/javaDocs.zip differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..b0d334a --- /dev/null +++ b/readme.md @@ -0,0 +1,61 @@ +# JAVA Fractal Library + +this library allow you to create fractal and render them in 2D. + +the library consist in one class named Fractal. + +## setup + +to install the library download the git, and add the file **Fractal.jar** to the environment variable **Classpath** + +## basic utilisation + +in order to use it you need to create a static method to compute the maximum fractal depth in which a point is in the fractal. Usualy the easiest way to do that is by making a recursive function. + +the fractal also need a dimension, this is use to ensure that the program will work as intented and prevent error. + +```java +import fractal.Fractal; + +public class Test{ + static int fractalInit(double[] coord,int maxDepth){ + //init method + return fractal(coord,maxDepth, inital argument); + } + + static int fractal(double[] coord,int maxDepth, more arguments){ + //something like this : + /** + * 1. Checking if not in fractal : return 0 + * 2. Checking if reach maxDepth : return 1 + * 3. recursive call + */ + } + + //we give the init method as argument of the fractal + public Fractal someFractal(2,Test::fractalInit) +} +``` + +the maximum depth of the fractal is an attribute of the class that can be modified. + +## rendering + +the fractal class can compute an instance of the java class BufferedImage of the fractal, to do that you have to specify the size of the Image, the color of the fractal and the background and the maximum depth which indicate how deep in the fractal a point need to be to be consider in the fractal. + +additionaly you can add information about the position of the camera, more precisely the origin of the camera (the origin is at the upper left corner of the image) and two vector indicating the direction along the X and Y axis corresponding the horizontal and vertical axis of the image. This system allow to render fractal with dimension higher than 2 by selecting a 2D section of a 3D or higher dimension space and redering the fractal in this plane. + +there are two main way of rendering the image with the class Fractal. + +the first method is the fastest, but is less precise as it compute only the point corresponding to the upper left point of every pixel. this can cause a pixel to be fully drawn or not drawn only because one of its point is or is not in the fractal. + +in the next image the pixel indicated in red will be black as the upper left corner of the pixel is in a dark area of the fractal even if the pixel content contain more white. +image + +to solve this issue, i've created another drawing method that use a random sampling of points in the pixel to determine on much the pixel should be drawn. + +here a comparaison of the render : + +image +image + diff --git a/source_code/fractal/Fractal.java b/source_code/fractal/Fractal.java new file mode 100644 index 0000000..11ee693 --- /dev/null +++ b/source_code/fractal/Fractal.java @@ -0,0 +1,286 @@ +package fractal; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.util.Random; +import java.util.function.BiFunction; + +/** + * + * this is a class to create fractal and render it + * but you can use it to draw other thing actualy. + * + * @author Physic Dev + * + * + * @version 1.0 + * + */ +public class Fractal { + //dimension of fractal coordinate (must be higher than the actual fractal) + private int dimension; + //maximum depth the fractal will compute + private int maxDepth=100; + //function that compute the maximum depth at which the given coordinate is in the fractal. + private BiFunction observer; + //used for the sampling drawing method. + private int sampling=10; + + /** + * + * @param dim the dimension of the fractal + * @param check a function that compute the maximum depth at which the given coordinate is in the fractal. + */ + public Fractal(int dim,BiFunction check) { + dimension=dim; + observer=check; + } + + /** + * dimension getter + * @return the dimension + */ + public int getDimension() { + return dimension; + } + + /** + * give the maximum depth of computing + * @return the maximum depth + */ + public int getMaxDepth() { + return maxDepth; + } + + /** + * get the sampling value + * @return the sampling value + */ + public int getSampling() { + return sampling; + } + + /** + * change the maximum depth of computing + * @param maxDpt the maximum depth + */ + public void setMaxDepth(int maxDpt) { + if(maxDpt<0) + throw new IllegalArgumentException("the maximum recursion depth must be positive"); + maxDepth=maxDpt; + } + + /** + * set the sampling value + * @param samp the sampling value + */ + public void setSampling(int samp) { + sampling =samp; + } + + /** + * compute the maximum depth in which the coordinate coord is in the fractal + * + * not that if the output is equal to maxDepth the coordinate may be deeper + * @param coord the coordinate + * @return the maximum depth at which the coordinate is in the fractal/ + */ + public int checkPix(double... coord){ + if(coord.length!=dimension) + throw new IllegalArgumentException("coordinate dimension ("+coord.length+") doesn't match fractal dimension : "+dimension); + return(observer.apply(coord, maxDepth)); + } + + /** + * Compute an image of the fractal + * + * to do so it will check take one coordinate for every pixel (the coordinate is located at the top left corner of the pixel) + * and will check if the pixel is deep enough in the fractal to be drawn. + * + * this method will not be precise for rendering little detail but is faster than the Sampling method. + * + * the fractal will be drawn from (0,0) to (1,1) + * + * @param width the width of the image + * @param height the height of the image + * @param c the color of the fractal + * @param back the color of the background + * @param depth any pixel lower than this depth will be drawn. + * @return a BufferedImage of the fractal + */ + public BufferedImage drawFractal(int width,int height,Color c,Color back,int depth) { + double[] Xvec=new double[dimension]; + double[] Yvec=new double[dimension]; + Xvec[0]=1.0;Yvec[1]=1.0; + return drawFractal(width,height,c,back,depth,new double[dimension],Xvec,Yvec); + } + + /** + * + * Compute an image of the fractal + * + * to do so it will check take one coordinate for every pixel (the coordinate is located at the top left corner of the pixel) + * and will check if the pixel is deep enough in the fractal to be drawn. + * + * this method will not be precise for rendering little detail but is faster than the Sampling method. + * + * in addition you can specify the part of the space you want to draw. + * + * note that you can draw 2D section of a fractal with dimension higher than 2. + * + * @param width the width of the image + * @param height the height of the image + * @param c the color of the fractal + * @param back the color of the background + * @param depth any pixel lower than this depth will be drawn. + * @param origin the origin from which the picture is drawn, the origin is located at the top left of the image + * @param Xvec the amount that would be draw along the X axis (horizontal left to right). the top right coordinate will be equal to the origin + Xvec + * @param Yvec the amount that would be draw along the Y axis (vertical top to bottom). the bottom left coordinate will be equal to the origin + Yvec + * + * @return a BufferedImage of the fractal + */ + public BufferedImage drawFractal(int width,int height,Color c,Color back,int depth,double[] origin,double[] Xvec,double[] Yvec){ + double[] coord=origin.clone(); + double[] startCol=origin.clone(); + + BufferedImage output=new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + WritableRaster raster = output.getRaster(); + int[] Bval=new int[] {back.getRed(),back.getGreen(),back.getBlue(),back.getAlpha()}; + int[] Cval=new int[] {c.getRed(),c.getGreen(),c.getBlue(),c.getAlpha()}; + for(int k=0;kdepth) + raster.setPixel(i, j, Cval); + else + raster.setPixel(i, j, Bval); + for(int k=0;kdepth)?1d:0d; + } + test=test/(double)sampling*light; + if(test>1) + raster.setPixel(i, j,new int[]{c.getRed(),c.getBlue(),c.getGreen(),c.getAlpha()}); + else + raster.setPixel(i, j,new int[]{(int) (test*(double)c.getRed()+(1d-test)*(double)back.getRed()), + (int) (test*(double)c.getGreen()+(1d-test)*back.getGreen()), + (int) (test*c.getBlue()+(1d-test)*back.getBlue()), + (int) (test*c.getAlpha()+(1d-test)*back.getAlpha())}); + //**/ + //raster.setPixel(i, j, new int[] {(int) (test*255d),(int) (test*255d),(int) (test*255d),255}); + for(int k=0;k=end) + return CantorSet(coord,MaxDepth-1,end-(start-end)*1d/3d,end-(start-end)*2d/3d)+1; + //not in the fractal + return 0; + } + + + /** + * initializer of the Sierpinsky carpet fractal + * use this method in the Fractal class + * + * @param coord the point + * @param MaxDepth the maximum depth to check + * @return the maximum depth where the point is in the fractal (or MaxDepth if the point is lower) + */ + public static int Menger(double[] coord,int MaxDepth) { + return Menger(coord,MaxDepth,1d/3d,2d/3d,1d/3d,2d/3d,1.0); + } + + + /** + * This is a simple recursive method that create a Menger fractal (also known as Sierpinsky carpet) + * + * this method is not very optimize and i think i can make it better. + * + * @param coord the point + * @param MaxDepth the maximum depth to check + * @param startX the lower bound X + * @param endX the upper bound X + * @param startY the lower bound Y + * @param endY the upper bound Y + * @param distance the size of the current block (divided by 3 because it's more convenient for the computing) + * @return the maximum depth where the point is in the fractal (or MaxDepth if the point is lower) + */ + public static int Menger(double[] coord,int MaxDepth,double startX,double endX,double startY,double endY,double distance) { + //middle square condition (out of the fractal) + if(coord[0]>startX && coord[0]startY && coord[1]endX) { + startX=endX+distance; + endX=startX+distance; + } + else { + startX+=distance; + endX=startX+distance; + } + } + distance=(endY-startY)/3d; + if(coord[1]endY) { + startY=endY+distance; + endY=startY+distance; + } + else { + startY+=distance; + endY=startY+distance; + } + } + return Menger(coord,MaxDepth-1,startX,endX,startY,endY,distance)+1 ; + } + /** + * the ratio used by the method Sierpinsky + */ + public static double ratio=2d; + + /** + * initializer of the Sierpinsky triangle fractal + * use this method in the Fractal class + * + * @param coord the point + * @param MaxDepth the maximum depth to check + * @return the maximum depth where the point is in the fractal (or MaxDepth if the point is lower) + */ + public static int Sierpinski(double[] coord,int MaxDepth) { + //initial check to see if we are in the main triangle. + if(ratio*(1-Math.abs(coord[0]))yLim) { + if(MaxDepth==1) + return 1; + return Sierpinski(coord,xLim,yLim+ratio*distance/2d,distance,MaxDepth-1)+1; + } + + //middle triangle (so not in the fractal) + if(Math.abs(coord[0]-xLim)*ratioxLim) + xLim+=distance; + else + xLim-=distance; + return Sierpinski(coord,xLim,yLim-ratio*distance/2d,distance,MaxDepth-1)+1; + } + +} diff --git a/source_code/module-info.java b/source_code/module-info.java new file mode 100644 index 0000000..3a633da --- /dev/null +++ b/source_code/module-info.java @@ -0,0 +1,9 @@ +/** + * + */ +/** + * + */ +module LittleProjects { + requires java.desktop; +} \ No newline at end of file