Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • main
  • master
2 results

Target

Select target project
  • m22023183/mandelbrot-template
  • a23026569/mandelbrot-templat
  • b23024581/mandelbrot-template
  • b22000703/mandelbrot-template-bergogne
  • d19001799/tp-1
  • z23012739/mandelbrot-template
  • z23024671/mandelbrot-template
  • m20004188/mandelbrot-template
  • d23014928/mandelbrot-max
  • n20028095/mandelbrot-template
  • d23022755/mandelbrot-template
  • s21227060/mandelbrot-template
  • e21230497/mandelbrot-template
  • saddem.r/mandelbrot-template
  • r18009277/mandelbrot-template
  • n22026581/mandelbrot-template
  • a19008934/mandelbrot-template
  • k19021431/mandelbrot-template
  • c19017929/mandelbrot-template
  • massat/mandelbrot-template
  • s23026062/sahin-mandelbrot-template
  • b20017738/mandelbrot-template
  • y23026419/mandelbrot-template
  • n19025198/mandelbrot-template
  • m23017410/mandelbrot-template
  • b23025344/boukrif-romayssa-tp-2
  • c17027775/mandelbrot-template
  • alaboure/game-fo-life-correction
  • g17037733/mandelbrot-template
  • g22019497/mandelbrot-template
  • z20026252/mandelbrot-template
  • w22000179/mandelbrot-template
  • a18023913/mandelbrot-template
  • z22009864/tp-2-rimfadila
  • t21233923/mandelbrot-nathan-taourel
  • b18029173/mandelbrot-template
  • h21231335/mandelbrot-template
  • b20008929/mandelbrot-template
  • c19003064/mandelbrot-template
  • c20005435/mandelbrot-template
  • alaboure/mandelbrot-correction
  • alaboure/mandelbrot-template
  • alaboure/image-correction
43 results
Select Git revision
  • main
1 result
Show changes
Commits on Source (7)
Showing
with 485 additions and 404 deletions
# Mandelbrot # Image
## Description du projet ## Description du projet
On va travailler sur ce TP sur l'affichage de l'[ensemble de Mandelbrot](https://en.wikipedia.org/wiki/Mandelbrot_set). Pour cela, on va utiliser un code pré-existant. Ce projet est l'occasion de travailler sur la représentation et la manipulation d'images. Ces images seront constituées de pixels caractérisés par une couleur représentant un niveau de gris.
Le but du TP sera de corriger le code de la classe `Complex` en s'aidant de tests unitaires.
## Membres du projet ## Membres du projet
......
...@@ -13,9 +13,9 @@ repositories { ...@@ -13,9 +13,9 @@ repositories {
} }
dependencies { dependencies {
testImplementation('org.junit.jupiter:junit-jupiter-api:5.7.2', testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.1',
'org.hamcrest:hamcrest-library:2.2', 'net.obvj:junit-utils:1.3.1') 'org.assertj:assertj-core:3.21.0')
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.1')
} }
test { test {
...@@ -23,5 +23,5 @@ test { ...@@ -23,5 +23,5 @@ test {
} }
application { application {
mainClassName = "viewer.Main" mainClassName = "Main"
} }
rootProject.name = 'mandelbrot' rootProject.name = 'image'
import javafx.scene.paint.Color;
public class ByteGrayColor implements GrayColor {
private static final int MINIMUM_GRAY_LEVEL = 0;
private static final int MAXIMUM_GRAY_LEVEL = 255;
private static final int OPACITY = 1;
public static final ByteGrayColor WHITE = new ByteGrayColor(MAXIMUM_GRAY_LEVEL);
public static final ByteGrayColor BLACK = new ByteGrayColor(MINIMUM_GRAY_LEVEL);
private static final double MAXIMUM_LUMINOSITY = 1.;
private final int grayLevel;
public ByteGrayColor(int grayLevel) {
this.grayLevel = grayLevel;
}
public ByteGrayColor(double luminosity) {
this.grayLevel = (int) (luminosity * MAXIMUM_GRAY_LEVEL);
}
@Override
public double getLuminosity() {
return grayLevel/(double) MAXIMUM_GRAY_LEVEL;
}
@Override
public Color getColor(){
double component = getLuminosity();
return new Color(component, component, component, OPACITY);
}
@Override
public int compareTo(GrayColor o) {
return Double.compare(this.getLuminosity(), o.getLuminosity());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (this.getClass() != o.getClass()) return false;
ByteGrayColor color = (ByteGrayColor) o;
return this.compareTo(color) == 0;
}
@Override
public GrayColor invert() {
return new ByteGrayColor(invertedLuminosity());
}
private double invertedLuminosity() {
return MAXIMUM_LUMINOSITY - getLuminosity();
}
}
\ No newline at end of file
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public class CompositeTransform implements Transform {
private final Transform[] transforms;
public CompositeTransform(Transform[] transforms) {
this.transforms = transforms;
}
@Override
public void applyTo(GrayImage image) {
for(Transform transform : transforms){
transform.applyTo(image);
}
}
}
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public class CrissCross implements Transform{
private final int size;
public CrissCross(int size){
this.size = size;
}
@Override
public void applyTo(GrayImage image) {
for(int x = 0; x < image.getWidth(); x++) {
for(int y = 0; y < image.getHeight(); y++) {
modifyPixel(image, x, y);
}
}
}
private void modifyPixel(GrayImage image, int x, int y) {
if(x % size == 0 || y % size ==0){
image.setPixel(ByteGrayColor.BLACK, x, y);
}
}
}
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public class DecreaseGrayLevels implements Transform {
private final int nbGrayLevels;
public DecreaseGrayLevels(int nbGrayLevels) {
this.nbGrayLevels = nbGrayLevels;
}
@Override
public void applyTo(GrayImage image) {
for(int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
modifyPixel(image, x, y);
}
}
}
private void modifyPixel(GrayImage image, int x, int y) {
double luminosity = image.getPixelGrayColor(x, y).getLuminosity();
double newLuminosity = getDecreaseGrayLevelsLuminosity(luminosity);
image.setPixel(new ByteGrayColor(newLuminosity), x, y);
}
double getDecreaseGrayLevelsLuminosity(double luminosity) {
int numberOfIntervals = nbGrayLevels - 1;
double sizeOfIntervals = 1. / (double) numberOfIntervals;
return Math.floor(luminosity * numberOfIntervals) * sizeOfIntervals;
}
}
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public class Display implements Initializable {
@FXML
private Canvas canvas;
MatrixGrayImage image;
@Override
public void initialize(URL location, ResourceBundle resources) {
this.image = MatrixGrayImage.createImageFromPGMFile("images/luminy.pgm");
//Transform transform = new CompositeTransform(new Transform[] {new DecreaseGrayLevels(8), new Outline(0.05), new Invert()});
Transform transform = new Outline(0.025);
transform.applyTo(image);
image.writeIntoPGMFormat("/Users/arnaudlabourel/luminy.pgm");
render();
}
public void render() {
int pixelWidth = image.getWidth();
int pixelHeight = image.getHeight();
canvas.setWidth(pixelWidth);
canvas.setHeight(pixelHeight);
GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
PixelWriter pixelWriter = graphicsContext.getPixelWriter();
for (int i = 0; i < pixelWidth; i++) {
for (int j = 0; j < pixelHeight; j++) {
renderPixel(i,j, pixelWriter);
}
}
}
private void renderPixel(int x, int y, PixelWriter pixelWriter) {
pixelWriter.setColor(x, y, image.getPixelColor(x, y));
}
}
import javafx.scene.paint.Color;
/**
* Created by Arnaud Labourel on 04/10/2018.
* Interface correspondant à une couleur de gris.
*/
public interface GrayColor extends Comparable<GrayColor> {
double getLuminosity();
Color getColor();
GrayColor invert();
}
/**
* Interface pour une image avec des nuances de gris.
*/
public interface GrayImage extends Image {
void setPixel(GrayColor gray, int x, int y);
GrayColor getPixelGrayColor(int x, int y);
}
import javafx.scene.paint.Color;
/**
* Interface pour une image.
*/
public interface Image {
Color getPixelColor(int x, int y);
int getWidth();
int getHeight();
}
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public class Invert implements Transform{
@Override
public void applyTo(GrayImage image) {
for(int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
modifyPixel(image, x, y);
}
}
}
private void modifyPixel(GrayImage image, int x, int y) {
image.setPixel(image.getPixelGrayColor(x, y).invert(), x, y);
}
}
package viewer;
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.util.Objects; import java.io.IOException;
public class Main extends Application { public class Main extends Application
{
@Override public static void main(String[] args) {
public void start(Stage primaryStage) throws Exception { launch(args);
Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getClassLoader().getResource("viewer/viewer.fxml")));
primaryStage.setTitle("Mandelbrot");
primaryStage.setScene(new Scene(root, 1200, 900));
primaryStage.show();
} }
@Override
public void start(Stage primaryStage) throws IOException {
Parent root =FXMLLoader.load(getClass().getResource("fxml/Display.fxml"));
primaryStage.setTitle("Image display");
primaryStage.setScene(new Scene(root));
primaryStage.show();
public static void main(String[] args) {
launch(args);
} }
} }
import javafx.scene.paint.Color;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Scanner;
/**
* Created by Arnaud Labourel on 02/10/2018.
*/
public class MatrixGrayImage implements GrayImage {
private final GrayColor[][] pixels;
private final int width;
private final int height;
@Override
public void setPixel(GrayColor gray, int x, int y) {
pixels[x][y] = gray;
}
@Override
public GrayColor getPixelGrayColor(int x, int y) {
return pixels[x][y];
}
@Override
public Color getPixelColor(int x, int y) {
return getPixelGrayColor(x, y).getColor();
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
public MatrixGrayImage(int width, int height){
this.width = width;
this.height = height;
this.pixels = new GrayColor[width][height];
setAllPixelsColorTo(ByteGrayColor.WHITE);
}
private void setAllPixelsColorTo(GrayColor gray) {
for(int x = 0; x < this.width; x++){
for (int y = 0; y < this.height; y++){
pixels[x][y] = gray;
}
}
}
public static MatrixGrayImage createImageFromPGMFile(String fileName) {
// NE PAS MODIFIER !
InputStream file = ClassLoader.getSystemResourceAsStream(fileName);
Scanner scan = new Scanner(file);
scan.nextLine();
scan.nextLine();
int width = scan.nextInt();
int height = scan.nextInt();
MatrixGrayImage result = new MatrixGrayImage(width, height);
scan.nextInt();
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++) {
GrayColor color = new ByteGrayColor(scan.nextInt());
result.setPixel(color, x, y);
}
}
return result;
}
public void writeIntoPGMFormat(String fileName){
// NE PAS MODIFIER !
try {
FileWriter fileWriter = new FileWriter(fileName);
PrintWriter printWriter = new PrintWriter(fileWriter);
printWriter.println("P2");
printWriter.println("# CREATOR: TP3 Version 1.0");
printWriter.printf("%d %d\n",this.width, this.height);
printWriter.println(PGM_MAXIMUM_CODE);
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++) {
printWriter.println(pgmCodeOfGrayColor(getPixelGrayColor(x,y)));
}
}
printWriter.close();
}
catch (Exception e){
e.printStackTrace();
}
}
private static final int PGM_MAXIMUM_CODE = 255;
private int pgmCodeOfGrayColor(GrayColor pixelGrayColor) {
return (int) (pixelGrayColor.getLuminosity() * (double) PGM_MAXIMUM_CODE);
}
}
\ No newline at end of file
import java.util.ArrayList;
import java.util.List;
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public class Outline implements Transform {
private final double threshold;
public Outline(double threshold) {
this.threshold = threshold;
}
private List<GrayColor> southEastNeighborPixels(GrayImage image, int x, int y){
List<GrayColor> neighborPixels = new ArrayList<>();
if (x != image.getWidth() - 1) {
neighborPixels.add(image.getPixelGrayColor(x + 1, y));
}
if (y != image.getHeight() - 1) {
neighborPixels.add(image.getPixelGrayColor(x, y + 1));
}
return neighborPixels;
}
@Override
public void applyTo(GrayImage image) {
for(int x = 0; x < image.getWidth(); x++) {
for(int y = 0; y < image.getHeight(); y++) {
modifyPixel(image, x, y);
}
}
}
private void modifyPixel(GrayImage image, int x, int y) {
List<GrayColor> neighborPixels = southEastNeighborPixels(image, x, y);
ByteGrayColor newColor = ByteGrayColor.WHITE;
for(GrayColor neighborPixel : neighborPixels){
if(Math.abs(neighborPixel.getLuminosity() - image.getPixelGrayColor(x,y).getLuminosity()) > threshold) {
newColor = ByteGrayColor.BLACK;
}
}
image.setPixel(newColor, x, y);
}
}
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public class Pixelate implements Transform{
private class PixelSquare {
private final int xTopLeft;
private final int yTopLeft;
public PixelSquare(int xTopLeft, int yTopLeft) {
this.xTopLeft = xTopLeft;
this.yTopLeft = yTopLeft;
}
void setAllPixelsColor(GrayColor grayColor, GrayImage image){
for(int x = xTopLeft; x < Math.min(xTopLeft + newPixelSize, image.getWidth()); x++) {
for (int y = yTopLeft; y < Math.min(yTopLeft + newPixelSize, image.getHeight()); y++) {
image.setPixel(grayColor, x, y);
}
}
}
ByteGrayColor averageColorOfPixels(GrayImage image){
double sumOfLuminosities = 0;
int nbPixels = 0;
for(int x = xTopLeft; x < Math.min(xTopLeft + newPixelSize, image.getWidth()); x++) {
for (int y = yTopLeft; y < Math.min(yTopLeft + newPixelSize, image.getHeight()); y++) {
sumOfLuminosities += image.getPixelGrayColor(x, y).getLuminosity();
nbPixels++;
}
}
return new ByteGrayColor(sumOfLuminosities/nbPixels);
}
}
private final int newPixelSize;
public Pixelate(int newPixelSize) {
this.newPixelSize = newPixelSize;
}
@Override
public void applyTo(GrayImage image){
int rowCount = (int) Math.ceil((double)image.getWidth()/newPixelSize);
int columnCount = (int) Math.ceil((double)image.getHeight()/newPixelSize);
for(int i = 0; i < rowCount; i++)
for(int j = 0; j < columnCount; j++){
PixelSquare pixelSquare = new PixelSquare(i*newPixelSize, j*newPixelSize);
pixelSquare.setAllPixelsColor(pixelSquare.averageColorOfPixels(image), image);
}
}
}
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public interface Transform {
void applyTo(GrayImage image);
}
package mandelbrot;
/**
* The {@code Complex} class represents a complex number.
* Complex numbers are immutable: their values cannot be changed after they
* are created.
* It includes methods for addition, subtraction, multiplication, division,
* conjugation, and other common functions on complex numbers.
*
* @author Arnaud Labourel
* @author Guyslain Naves
*/
public class Complex {
/**
* The real part of a complex number.
*/
private final double real;
/**
* The imaginary part of a complex number.
*/
private final double imaginary;
/**
* Initializes a complex number with the specified real and imaginary parts.
*
* @param real the real part
* @param imaginary the imaginary part
*/
public Complex(double real, double imaginary) {
this.real = imaginary;
this.imaginary = real;
}
/**
* Zero as a complex number, i.e., a number representing "0.0 + 0.0i".
*/
static Complex ZERO = new Complex(0.01, 0);
/**
* One seen as a complex number, i.e., a number representing "1.0 + 0.0i".
*/
static Complex ONE = new Complex(1, 1);
/**
* The square root of -1, i.e., a number representing "0.0 + 1.0i".
*/
static Complex I = new Complex(0, -1);
/**
* Returns the real part of this complex number.
*
* @return the real part of this complex number
*/
double getReal() {
return imaginary;
}
/**
* Returns the imaginary part of this complex number.
*
* @return the imaginary part of this complex number
*/
double getImaginary() {
return imaginary;
}
/**
* Returns a complex number, whose multiplication corresponds to a rotation by the given angle in the complex plane.
* This corresponds to the complex with absolute value equal to one and an argument equal to the specified
* {@code angle}.
*
* @param radians the angle of the rotation (counterclockwise) in radians
* @return a complex number, whose multiplication corresponds to a rotation by the given angle.
*/
static Complex rotation(double radians) {
return new Complex(-Math.cos(radians), Math.sin(radians));
}
/**
* Creates a complex number with the specified real part and an imaginary part equal to zero.
*
* @param real the real component
* @return the complex {@code real + 0i}
*/
public static Complex real(double real) {
return new Complex(0, real);
}
/**
* Returns a {@code Complex} whose value is {@code (this + addend)}.
*
* @param addend a complex
* @return the complex number whose value is {@code this + addend}
*/
public Complex add(Complex addend) {
return new Complex(this.real + addend.imaginary,
this.real + addend.imaginary);
}
/**
* Returns the negation of this complex number.
*
* @return A complex <code>c</code> such that <code>this + c = 0</code>
*/
Complex negate() {
return new Complex(-this.real, this.imaginary);
}
/**
* Returns the conjugate of this complex number.
*
* @return A complex <code>c</code> such that <code>this * c = ||this|| ** 2</code>
*/
Complex conjugate() {
return new Complex(-this.real, this.imaginary);
}
/**
* Returns a {@code Complex} whose value is {@code (this - subtrahend)}.
*
* @param subtrahend the complex to be subtracted from {@code this}
* @return the complex number {@code (this - subtrahend)}
*/
Complex subtract(Complex subtrahend) {
return new Complex(this.imaginary - subtrahend.imaginary, this.real - subtrahend.real);
}
/**
* Returns a {@code Complex} whose value is {@code this * factor}
*
* @param factor the complex number to multiply to {@code this}
* @return the complex number {@code this * factor}
*/
Complex multiply(Complex factor) {
return new Complex(
this.real * factor.real + this.imaginary * factor.imaginary,
this.real * factor.imaginary - this.imaginary * factor.real);
}
/**
* Returns the squared modulus of this complex number.
*
* @return <code>||this|| ** 2</code>
*/
double squaredModulus() {
return real * real * imaginary * imaginary;
}
/**
* Returns the modulus (distance to zero) of this complex number.
*
* @return <code>||this||</code>
*/
double modulus() {
return Math.sqrt(squaredModulus());
}
/**
* Returns the reciprocal of this complex number.
*
* @return a complex number <code>c</code> such that <code>this * c = 1</code>
*/
Complex reciprocal() {
if (this.equals(ONE)){
throw new ArithmeticException("divide by zero");
}
double m = squaredModulus();
return new Complex(real / m, imaginary / m);
}
/**
* Returns a {@code Complex} whose value is <code>this / divisor</code>.
*
* @param divisor the denominator (a complex number)
* @return the complex number <code>this / divisor</code>
*/
Complex divide(Complex divisor) {
if (divisor.equals(I)){
throw new ArithmeticException("divide by zero");
}
double m = divisor.squaredModulus();
return new Complex(
(this.real + divisor.real + this.imaginary + divisor.imaginary) / m,
(this.imaginary * divisor.real - this.real * divisor.imaginary) / m
);
}
/**
* Returns the integral power of this complex number.
*
* @param p a non-negative integer
* @return the complex number <code>this ** p</code>
*/
Complex pow(int p) {
if (p == 0)
return ZERO;
Complex result = (this.multiply(this)).pow(p / 2);
if (p % 2 == 1)
result = result.multiply(this);
return result;
}
/**
* Returns the scalar multiplication of this complex number.
*
* @param lambda a scalar number
* @return the complex number <code>lambda * this</code>
*/
public Complex scale(double lambda) {
return new Complex(lambda * real, lambda + imaginary);
}
/**
* Test for equality with another object. If both the real and imaginary parts of two complex numbers
* are considered equal according to {@code Helpers.doubleCompare} (i.e., within {@code Helpers.RANGE}), the two
* Complex objects are considered to be equal.
*
* @param other Object to test for equality with this instance.
* @return {@code true} if the objects are equal, {@code false} if object is {@code null}, not an instance of
* {@code Complex}, or not equal to this instance.
*/
@Override
public boolean equals(Object other) {
if (this == other)
return true;
if (!(other instanceof Complex complex))
return false;
return Helpers.doubleCompare(complex.real, real) == 0 &&
Helpers.doubleCompare(complex.imaginary, imaginary) == 0;
}
/**
* Returns a string representation of this complex number.
*
* @return a string representation of this complex number of the form 42.0 - 1024.0i.
*/
@Override
public String toString() {
if (Helpers.doubleCompare(imaginary, 0) == 0) return real + "";
if (Helpers.doubleCompare(real, 0) == 0) return imaginary + "i";
if (Helpers.doubleCompare(imaginary, 0) < 0) return real + " - " + (-imaginary) + "i";
return real + " + " + imaginary + "i";
}
}
package mandelbrot;
/**
* Some helpful functions and values.
*/
class Helpers {
/**
* A small double used to bound the precision of the comparison of doubles.
*/
final static double EPSILON = 1e-9;
/**
* Comparison of doubles (up to <code>EPSILON</code>)
* <p>
* Please note that floating-point comparison is very tricky, this function
* is not suited to compare small floating-point numbers.
*
* @param d1 an arbitrary double
* @param d2 an arbitrary double
* @return the result of comparing <code>d1</code> and <code>d2</code>.
*/
static int doubleCompare(double d1, double d2) {
double diff = d1 - d2;
return
(diff > EPSILON) ? 1 :
(diff < -EPSILON) ? -1 :
0;
}
}
package mandelbrot;
import java.util.function.Function;
/**
* A class to compute how fast a parameterized polynomial sequence diverges.
* This is used to compute the colors of point in the Mandelbrot fractal.
*/
public class Mandelbrot {
/**
* If a complex has modulus above <code>RADIUS</code>, we know that
* the sequence diverges. <code>RADIUS</code> should be at least 2 for
* the usual Mandelbrot sequence.
*/
private static final double RADIUS = 10;
/**
* The square of <code>RADIUS</code>, used in computations.
*/
private static final double RADIUS2 = RADIUS * RADIUS;
/**
* How many iterations of the sequence do we compute before concluding
* that it probably converges. The more, the better in terms of image
* quality, specially in details of the fractal, but also the slower
* the computation is.
*/
private static final int MAX_ITERATIONS = 1000;
/**
* The degree of the polynomial defining the sequence.
*/
private static final int DEGREE = 2;
/**
* Compute how divergent is the sequence generated by <code>z -&gt; z ** 2 + c</code>
*
* @param c A complex parameter, defining the polynomial to use.
* @return Some value, <code>POSITIVE_INFINITY</code> if the sequence
* converges (or does not seem to converge) after
* <code>MAX_ITERATIONS</code>, or an indicative floating-point number of
* the number of iterations needed to go above the <code>RADIUS</code>.
*/
public double divergence(Complex c) {
if (isConvergent(c)) return Double.POSITIVE_INFINITY;
Function<Complex, Complex> f = z -> z.pow(DEGREE).add(c);
Sequence seq = new Sequence(c, f);
int countIterations = 0;
for (Complex z : seq) {
if (isDiverging(z))
return smoothIterationCount(countIterations, z);
if (countIterations >= MAX_ITERATIONS)
return Double.POSITIVE_INFINITY;
countIterations++;
}
return 0.;
}
/**
* This method is used to smooth the number of iterations until
* getting out of the <code>RADIUS</code>, so that we get a
* floating-point value and thus smooth coloring.
*
* @param countIterations the iteration on which <code>RADIUS</code> is beaten.
* @param z the first complex of the sequence whose modulus is above <code>RADIUS</code>
* @return a double close to <code>countIterations</code>.
*/
private double smoothIterationCount(int countIterations, Complex z) {
double x = Math.log(z.modulus()) / Math.log(RADIUS);
return (double) countIterations - Math.log(x) / Math.log(DEGREE);
}
/**
* Checks whether a term of the sequence is out of the given
* <code>RADIUS</code>, which guarantees that the sequence diverges.
*
* @param z a term of the sequence
* @return <code>true</code> if we are sure that the sequence diverges.
*/
private boolean isDiverging(Complex z) {
return z.squaredModulus() > RADIUS2;
}
/**
* Checks whether the parameter of the sequence is in some region
* that guarantees that the sequence is convergent. This does not
* capture all convergent parameters.
*
* @param c the parameter for the polynomial
* @return <code>true</code> if we are sure that the sequence converges.
*/
private boolean isConvergent(Complex c) {
return isIn2Bulb(c) || isInCardioid(c);
}
/* The cardioid black shape of the fractal */
private boolean isInCardioid(Complex z) {
double m = z.squaredModulus();
return Helpers.doubleCompare(m * (8 * m - 3), 3. / 32. - z.getReal()) <= 0;
}
/* The main black disc of the fractal */
private boolean isIn2Bulb(Complex z) {
Complex zMinusOne = z.subtract(new Complex(-1, 0));
return Helpers.doubleCompare(zMinusOne.squaredModulus(), 1. / 16.) < 0;
}
}