Initial commit
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Gradle build configuration for specific lab module / exercise
|
||||
*/
|
||||
// the Java plugin is added by default in the main lab configuration
|
||||
plugins {
|
||||
id 'java'
|
||||
// Apply the application plugin to add support for building a CLI application.
|
||||
id 'application'
|
||||
// Adding JavaFX support and dependencies
|
||||
id 'org.openjfx.javafxplugin' version '0.0.11'
|
||||
}
|
||||
// Project/Module information
|
||||
description = 'Lab01 Mandelbrot'
|
||||
group = 'ch.zhaw.prog2'
|
||||
version = '2022.1'
|
||||
|
||||
// Dependency configuration
|
||||
// repositories to download dependencies from
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
// required dependencies
|
||||
dependencies {
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.3.1'
|
||||
}
|
||||
|
||||
// Test task configuration
|
||||
test {
|
||||
// Use JUnit platform for unit tests
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
// Configuration for Application plugin
|
||||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass = 'ch.zhaw.prog2.mandelbrot.Mandelbrot'
|
||||
}
|
||||
|
||||
// Configuration for JavaFX plugin
|
||||
javafx {
|
||||
version = '17'
|
||||
modules = [ 'javafx.controls', 'javafx.fxml' ]
|
||||
}
|
||||
|
||||
// Java plugin configuration
|
||||
java {
|
||||
// By default the Java version of the gradle process is used as source/target version.
|
||||
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||
|
||||
// Java compiler specific options
|
||||
compileJava {
|
||||
// source files should be UTF-8 encoded
|
||||
options.encoding = 'UTF-8'
|
||||
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ch.zhaw.prog2.mandelbrot;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
/**
|
||||
* This is a container class which holds the data for one row to be drawn on the canvas.
|
||||
* No getter and setters. Just use direct access to the fields.
|
||||
*/
|
||||
public class ImageRow {
|
||||
public final int rowNumber;
|
||||
public final Color[] pixels;
|
||||
|
||||
public ImageRow(int rowNumber, int width) {
|
||||
this.rowNumber = rowNumber;
|
||||
this.pixels = new Color[width];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ch.zhaw.prog2.mandelbrot;
|
||||
|
||||
import javafx.application.Application;
|
||||
|
||||
/**
|
||||
* This application uses several threads to compute an image "in the background".
|
||||
*
|
||||
* As rows of pixels in the image are computed, they are copied to the screen.
|
||||
* (The image is a small piece of the famous Mandelbrot set, which
|
||||
* is used just because it takes some time to compute. There is no need
|
||||
* to understand what the image means.) The user starts the computation by
|
||||
* clicking a "Start" button. A pop-up menu allows the user to select the
|
||||
* number of threads to be used. The specified number of threads is created
|
||||
* and each thread is assigned a region in the image. The threads are run
|
||||
* at lower priority, which will make sure that the GUI thread will get a
|
||||
* chance to run to repaint the display as necessary.
|
||||
*/
|
||||
public class Mandelbrot {
|
||||
|
||||
/**
|
||||
* This Wrapper Class is only required to allow IDEs to start the FX-Applications
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
Application.launch(MandelbrotGui.class, args);
|
||||
}
|
||||
|
||||
} // end Mandelbrot
|
||||
@@ -0,0 +1,206 @@
|
||||
package ch.zhaw.prog2.mandelbrot;
|
||||
|
||||
import ch.zhaw.prog2.mandelbrot.processors.*;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* This application uses several threads to compute an image "in the background".
|
||||
*
|
||||
* As rows of pixels in the image are computed, they are copied to the screen.
|
||||
* (The image is a small piece of the famous Mandelbrot set, which
|
||||
* is used just because it takes some time to compute. There is no need
|
||||
* to understand what the image means.) The user starts the computation by
|
||||
* clicking a "Start" button. A pop-up menu allows the user to select the
|
||||
* number of threads to be used. The specified number of threads is created
|
||||
* and each thread is assigned a region in the image. The threads are run
|
||||
* at lower priority, which will make sure that the GUI thread will get a
|
||||
* chance to run to repaint the display as necessary.
|
||||
*/
|
||||
public class MandelbrotGui extends Application implements MandelbrotProcessorListener {
|
||||
// possible states of the GUI
|
||||
private enum GuiState {START, RUNNING, STOPPING, STOPPED}
|
||||
|
||||
private GuiState state = GuiState.START;
|
||||
|
||||
private MandelbrotProcessor processor;
|
||||
|
||||
private Button startButton; // button the user can click to start or abort the thread
|
||||
private ComboBox<String> threadCountSelect; // for specifying the number of threads to be used
|
||||
private ComboBox<String> processorSelect; // for specifying the processor
|
||||
|
||||
private Canvas canvas; // the canvas where the image is displayed
|
||||
private GraphicsContext g; // the graphics context for drawing on the canvas
|
||||
|
||||
private int width, height; // the size of the canvas
|
||||
|
||||
|
||||
/**
|
||||
* Set up the GUI and event handling. The canvas will be 1200-by-1000 pixels,
|
||||
* if that fits comfortably on the screen; otherwise, size will be reduced to fit.
|
||||
*/
|
||||
public void start(Stage stage) {
|
||||
int screenWidth = (int) Screen.getPrimary().getVisualBounds().getWidth();
|
||||
int screenHeight = (int) Screen.getPrimary().getVisualBounds().getHeight();
|
||||
width = Math.min(1200, screenWidth - 50);
|
||||
height = Math.min(1000, screenHeight - 120);
|
||||
|
||||
canvas = new Canvas(width, height);
|
||||
g = canvas.getGraphicsContext2D();
|
||||
g.setFill(Color.LIGHTGRAY);
|
||||
g.fillRect(0, 0, width, height);
|
||||
startButton = new Button("Start!");
|
||||
startButton.setOnAction(e -> startOrStopProcessing());
|
||||
// Because the tasks are CPU intensive, the default number of threads should be equal to the number of cores
|
||||
int numCores = Runtime.getRuntime().availableProcessors();
|
||||
int maxThreads = 2 * numCores;
|
||||
threadCountSelect = new ComboBox<>();
|
||||
threadCountSelect.setEditable(false);
|
||||
for (int i = 1; i <= maxThreads; i++) {
|
||||
threadCountSelect.getItems().add("Use " + i + " threads.");
|
||||
}
|
||||
// use the number of cores as default value
|
||||
threadCountSelect.getSelectionModel().select(numCores - 1);
|
||||
|
||||
processorSelect = new ComboBox<>();
|
||||
processorSelect.setEditable(false);
|
||||
processorSelect.getItems().add("Use Thread-Processor");
|
||||
processorSelect.getItems().add("Use Executor-Processor");
|
||||
processorSelect.getItems().add("Use Callable-Processor");
|
||||
processorSelect.getSelectionModel().select(0);
|
||||
|
||||
HBox bottom = new HBox(8, startButton, threadCountSelect, processorSelect);
|
||||
bottom.setStyle("-fx-padding: 6px; -fx-border-color:black; -fx-border-width: 2px 0 0 0");
|
||||
bottom.setAlignment(Pos.CENTER);
|
||||
BorderPane root = new BorderPane(canvas);
|
||||
root.setBottom(bottom);
|
||||
root.setStyle("-fx-border-color:black; -fx-border-width: 2px");
|
||||
Scene scene = new Scene(root);
|
||||
stage.setScene(scene);
|
||||
stage.setTitle("Mandelbrot");
|
||||
stage.setResizable(false);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is called when the user clicks the start button.
|
||||
* If no computation is currently running, it starts processing with
|
||||
* as many new threads as the user has specified.
|
||||
* If a computation is in progress when this method is called,
|
||||
* the processing is stopped and threads should be terminated.
|
||||
*/
|
||||
private void startOrStopProcessing() {
|
||||
if (state == GuiState.RUNNING) {
|
||||
setState(GuiState.STOPPING);
|
||||
if (processor != null) {
|
||||
processor.stopProcessing();
|
||||
}
|
||||
} else {
|
||||
setState(GuiState.RUNNING);
|
||||
int threadCount = threadCountSelect.getSelectionModel().getSelectedIndex() + 1;
|
||||
processor = getProcessor();
|
||||
System.out.println("Start processing using " + processor.getClass().getSimpleName() +
|
||||
" with " + threadCount + " threads.");
|
||||
// run processing in its own thread
|
||||
new Thread(() -> processor.startProcessing(threadCount)).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the selected MandelbrotProcessor implementation.
|
||||
*
|
||||
* @return new Instance of the selected MandelbrotProcessor implementation
|
||||
*/
|
||||
private MandelbrotProcessor getProcessor() {
|
||||
MandelbrotProcessor processor;
|
||||
int processorIndex = processorSelect.getSelectionModel().getSelectedIndex();
|
||||
switch (processorIndex) {
|
||||
case 0: processor = new MandelbrotTaskProcessor(this, width, height); break;
|
||||
case 1: processor = new MandelbrotExecutorProcessor(this, width, height); break;
|
||||
case 2: processor = new MandelbrotCallableProcessor(this, width, height); break;
|
||||
default: processor = new MandelbrotTaskProcessor(this, width, height); break;
|
||||
}
|
||||
return processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update GUI elements for a specific state
|
||||
*
|
||||
* @param newState the new state the GUI should be configured for
|
||||
*/
|
||||
private void setState(GuiState newState) {
|
||||
this.state = newState;
|
||||
switch (newState) {
|
||||
case RUNNING:
|
||||
Platform.runLater(() -> {
|
||||
startButton.setText("Abort"); // change name while computation is in progress
|
||||
threadCountSelect.setDisable(true); // will be re-enabled when all threads finish
|
||||
processorSelect.setDisable(true); // will be re-enabled when all threads finish
|
||||
g.setFill(Color.LIGHTGRAY); // fill canvas with gray
|
||||
g.fillRect(0, 0, width, height);
|
||||
});
|
||||
break;
|
||||
case STOPPING:
|
||||
Platform.runLater(() -> {
|
||||
// prevent user from trying to stop threads that are already stopping
|
||||
startButton.setDisable(true); // will be re-enabled when all threads have stopped
|
||||
});
|
||||
break;
|
||||
case STOPPED:
|
||||
Platform.runLater(() -> {
|
||||
// Make sure state is correct when threads end.
|
||||
startButton.setText("Start Again");
|
||||
startButton.setDisable(false);
|
||||
threadCountSelect.setDisable(false);
|
||||
processorSelect.setDisable(false);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from the Processor when the processing is stopped or completed.
|
||||
* GUI state needs to be reset.
|
||||
*
|
||||
* @param duration Duration of the processing in Milliseconds
|
||||
*/
|
||||
@Override
|
||||
public void processingStopped(long duration) {
|
||||
this.setState(GuiState.STOPPED);
|
||||
System.out.println("Finished processing after " + duration + "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from the Processor when a row has been processed
|
||||
* and needs to be added to the image.
|
||||
*
|
||||
* @param row the row of pixels whose colors are to be set
|
||||
*/
|
||||
@Override
|
||||
public void rowProcessed(ImageRow row) {
|
||||
if (row != null) {
|
||||
Platform.runLater(() -> {
|
||||
for (int x = 0; x < row.pixels.length; x++) {
|
||||
// Color an individual pixel by filling in a 1-by-1 pixel rectangle.
|
||||
g.setFill(row.pixels[x]);
|
||||
g.fillRect(x, row.rowNumber, 1, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // end MandelbrotGui
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package ch.zhaw.prog2.mandelbrot;
|
||||
|
||||
public interface MandelbrotProcessorListener {
|
||||
/**
|
||||
* This method is called from the Processor when the processing is stopped or completed.
|
||||
* GUI state needs to be reset.
|
||||
*
|
||||
* @param duration Duration of the processing in Milliseconds
|
||||
*/
|
||||
void processingStopped(long duration);
|
||||
|
||||
/**
|
||||
* This method is called from the Processor when a row has been processed
|
||||
* and needs to be added to the image.
|
||||
*
|
||||
* @param row the row of pixels whose colors are to be set
|
||||
*/
|
||||
void rowProcessed(ImageRow row);
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package ch.zhaw.prog2.mandelbrot.processors;
|
||||
|
||||
import ch.zhaw.prog2.mandelbrot.ImageRow;
|
||||
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class MandelbrotCallableProcessor extends MandelbrotProcessor {
|
||||
|
||||
private volatile boolean terminate; // signal the threads to abort processing and terminate
|
||||
|
||||
/**
|
||||
* Initialize the Mandelbrot processor.
|
||||
* This method also initializes the color palette, containing colors in spectral order.
|
||||
* @param processorListener class to notify about processing results
|
||||
* @param width with of the canvas in pixel
|
||||
* @param height height of the canvas in pixel
|
||||
*/
|
||||
public MandelbrotCallableProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
|
||||
super(processorListener, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts as many new threads as the user has specified,
|
||||
* and assigns a different part of the image to each thread.
|
||||
* The threads are run at lower priority than the event-handling thread,
|
||||
* in order to keep the GUI responsive.
|
||||
*
|
||||
* @param numThreads number of thread to start to run the tasks
|
||||
*/
|
||||
@Override
|
||||
public void startProcessing(int numThreads) {
|
||||
terminate = false; // Set the signal before starting the threads!
|
||||
super.tasksRemaining = height; // Records how many of the threads are still running
|
||||
super.startTime = System.currentTimeMillis();
|
||||
|
||||
// TODO: Start the the executor service with the given number of threads.
|
||||
|
||||
// TODO: process all rows using the Callable MandelbrotTask and store returned Futures in a list
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
long duration = System.currentTimeMillis()-startTime;
|
||||
System.out.println("Tasks submitted after " + duration + "ms");
|
||||
|
||||
// TODO: get results from Future list and send them to the processListener (GUI)
|
||||
// make sure to handle all Exceptions
|
||||
try {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} finally {
|
||||
// stop processing and shutdown executor
|
||||
stopProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stopp processing tasks and terminate all threads.
|
||||
* Also notifies the GUI that the processing has been stopped.
|
||||
*/
|
||||
@Override
|
||||
public void stopProcessing() {
|
||||
terminate = true; // signal the threads to abort
|
||||
// TODO: shutdown executor service
|
||||
|
||||
|
||||
|
||||
// calculate processing time
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
// notify the listener that the processing is completed
|
||||
processorListener.processingStopped(duration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This class defines the thread that does the computation.
|
||||
* The run method computes the image one pixel at a time.
|
||||
* After computing the colors for each row of pixels, the colors are
|
||||
* copied into the image, and the part of the display that shows that
|
||||
* row is repainted.
|
||||
*
|
||||
* Extended to implement also Callable.
|
||||
* the call() method calculates and return only the result for the startRow.
|
||||
*/
|
||||
private class MandelbrotTask implements Callable<ImageRow> {
|
||||
// TODO: Use Task implementation from MandelbrotExecutorProcessor change it to a Callable.
|
||||
|
||||
|
||||
public ImageRow call() {
|
||||
return null; // Compute one row of pixels.
|
||||
}
|
||||
|
||||
} // end MandelbrotTask
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package ch.zhaw.prog2.mandelbrot.processors;
|
||||
|
||||
import ch.zhaw.prog2.mandelbrot.ImageRow;
|
||||
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class MandelbrotExecutorProcessor extends MandelbrotProcessor {
|
||||
|
||||
private volatile boolean terminate; // signal the threads to abort processing and terminate
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the Mandelbrot processor.
|
||||
* This method also initializes the color palette, containing colors in spectral order.
|
||||
* @param processorListener class to notify about processing results
|
||||
* @param width with of the canvas in pixel
|
||||
* @param height height of the canvas in pixel
|
||||
*/
|
||||
public MandelbrotExecutorProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
|
||||
super(processorListener, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts as many new threads as the user has specified,
|
||||
* and assigns a different part of the image to each thread.
|
||||
* The threads are run at lower priority than the event-handling thread,
|
||||
* in order to keep the GUI responsive.
|
||||
*
|
||||
* @param numThreads number of thread to start to run the tasks
|
||||
*/
|
||||
@Override
|
||||
public void startProcessing(int numThreads) {
|
||||
terminate = false; // Set the signal before starting the threads!
|
||||
super.tasksRemaining = height; // Records how many of the threads are still running
|
||||
super.startTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
// TODO: Start the the executor service with the given number of threads.
|
||||
|
||||
// TODO: start a task for each row
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stopp processing tasks and terminate all threads.
|
||||
* Also notifies the GUI that the processing has been stopped.
|
||||
*/
|
||||
@Override
|
||||
public void stopProcessing() {
|
||||
terminate = true; // signal the threads to abort
|
||||
// TODO: shutdown executor service
|
||||
|
||||
// calculate processing time
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
// notify the listener that the processing is completed
|
||||
processorListener.processingStopped(duration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This class defines the thread that does the computation.
|
||||
* The run method computes the image one pixel at a time.
|
||||
* After computing the colors for each row of pixels, the colors are
|
||||
* copied into the image, and the part of the display that shows that
|
||||
* row is repainted.
|
||||
*/
|
||||
private class MandelbrotTask implements Runnable {
|
||||
// TODO: Use Task implementation from MandelbrotTaskProcessor and modify it to calculat only one row.
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
}
|
||||
} // end MandelbrotTask
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package ch.zhaw.prog2.mandelbrot.processors;
|
||||
|
||||
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public abstract class MandelbrotProcessor {
|
||||
protected final MandelbrotProcessorListener processorListener;
|
||||
protected javafx.scene.paint.Color[] palette; // the color palette, containing the colors of the spectrum
|
||||
protected int width; // width of the canvas
|
||||
protected int height; //height of the canvas
|
||||
protected long startTime; // used to calculate the runtime for the calculation
|
||||
protected int tasksRemaining; // How many tasks/threads are still running resp. need to be processed
|
||||
|
||||
/**
|
||||
* Initialize the Mandelbrot processor.
|
||||
* This method also initializes the color palette, containing colors in spectral order.
|
||||
* @param processorListener class to notify about processing results
|
||||
* @param width with of the canvas in pixel
|
||||
* @param height height of the canvas in pixel
|
||||
*/
|
||||
public MandelbrotProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
|
||||
this.processorListener = processorListener;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
// initialize the color palette
|
||||
this.palette = new Color[256];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
this.palette[i] = Color.hsb(360 * (i / 256.0), 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts as many new threads as the user has specified,
|
||||
* and assigns a different part of the image to each thread.
|
||||
*
|
||||
* @param numThreads number of thread to start to run the tasks
|
||||
*/
|
||||
public abstract void startProcessing(int numThreads);
|
||||
|
||||
/**
|
||||
* Stopp processing tasks and terminate all threads.
|
||||
* Also notifies the GUI that the processing has been stopped.
|
||||
*/
|
||||
public abstract void stopProcessing();
|
||||
|
||||
/**
|
||||
* This method is called by each task/thread when it terminates. We keep track
|
||||
* of the number of tasks/threads that have terminated, so that when they have
|
||||
* all finished, we can put the program into the correct state, such as
|
||||
* changing the name of the button to "Start Again" and re-enabling the
|
||||
* pop-up menu.
|
||||
*/
|
||||
protected synchronized void taskFinished() {
|
||||
tasksRemaining--;
|
||||
if (tasksRemaining == 0) { // all threads have finished
|
||||
stopProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
package ch.zhaw.prog2.mandelbrot.processors;
|
||||
|
||||
import ch.zhaw.prog2.mandelbrot.ImageRow;
|
||||
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public class MandelbrotTaskProcessor extends MandelbrotProcessor {
|
||||
|
||||
private volatile boolean terminate; // signal the threads to abort processing and terminate
|
||||
private java.lang.Thread[] workers; // the threads that compute the image
|
||||
|
||||
/**
|
||||
* Initialize the Mandelbrot processor.
|
||||
* This method also initializes the color palette, containing colors in spectral order.
|
||||
* @param processorListener class to notify about processing results
|
||||
* @param width with of the canvas in pixel
|
||||
* @param height height of the canvas in pixel
|
||||
*/
|
||||
public MandelbrotTaskProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
|
||||
super(processorListener, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts as many new threads as the user has specified,
|
||||
* and assigns a different part of the image to each thread.
|
||||
* The threads are run at lower priority than the event-handling thread,
|
||||
* in order to keep the GUI responsive.
|
||||
*
|
||||
* @param numThreads number of thread to start to run the tasks
|
||||
*/
|
||||
@Override
|
||||
public void startProcessing(int numThreads) {
|
||||
terminate = false; // Set the signal before starting the threads!
|
||||
// use numThread tasks each calculating a range of rows
|
||||
super.tasksRemaining = numThreads; // Records how many of the threads are still running
|
||||
super.startTime = System.currentTimeMillis();
|
||||
// calculate number of rows each task needs to calculate
|
||||
int rowsPerThread = height / numThreads;
|
||||
|
||||
// Start the given number of threads and process ranges of rows
|
||||
workers = new Thread[numThreads];
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
// first row computed by thread number i
|
||||
int startRow = rowsPerThread * i;
|
||||
|
||||
// last row computed by thread number i
|
||||
// (we have to make sure that the endRow for the last thread is the bottom row of the image)
|
||||
int endRow = (i == numThreads - 1) ? height - 1 : rowsPerThread * (i + 1) - 1;
|
||||
|
||||
// Create and start a thread to compute the rows of the image from startRow to endRow.
|
||||
workers[i] = new Thread(new MandelbrotTask(startRow, endRow));
|
||||
try {
|
||||
workers[i].setPriority(Thread.currentThread().getPriority() - 1);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error: " + e.getMessage());
|
||||
}
|
||||
workers[i].start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stopp processing tasks and terminate all threads.
|
||||
* Also notifies the GUI that the processing has been stopped.
|
||||
*/
|
||||
@Override
|
||||
public void stopProcessing() {
|
||||
terminate = true; // signal the threads to abort
|
||||
// shutdown / unset workers
|
||||
workers = null;
|
||||
// calculate processing time
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
// notify the listener that the processing is completed
|
||||
processorListener.processingStopped(duration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This class defines the thread that does the computation.
|
||||
* The run method computes the image one pixel at a time.
|
||||
* After computing the colors for each row of pixels, the colors are
|
||||
* copied into the image, and the part of the display that shows that
|
||||
* row is repainted.
|
||||
* All modifications to the GUI are made using Platform.runLater().
|
||||
* (Since the thread runs in the background, at lower priority than
|
||||
* the event-handling thread, the event-handling thread wakes up
|
||||
* immediately to repaint the display.)
|
||||
*/
|
||||
private class MandelbrotTask implements Runnable {
|
||||
// these values define the area and depth of the Mandelbrot graphic
|
||||
// we keep them local to allow to extend the function to
|
||||
// select the area and depth dynamically.
|
||||
private final double xmin, xmax, ymin, ymax, dx, dy;
|
||||
private final int maxIterations;
|
||||
// this tasks calculates the following range of rows
|
||||
private final int startRow, endRow;
|
||||
|
||||
/** initialize the Task to calculate a single row */
|
||||
MandelbrotTask(int row) {
|
||||
this(row, row);
|
||||
}
|
||||
|
||||
/** initialize the Task to calculate a range of rows */
|
||||
MandelbrotTask(int startRow, int endRow) {
|
||||
this.startRow = startRow;
|
||||
this.endRow = endRow;
|
||||
xmin = -1.6744096740931858;
|
||||
xmax = -1.674409674093473;
|
||||
ymin = 4.716540768697223E-5;
|
||||
ymax = 4.716540790246652E-5;
|
||||
dx = (xmax - xmin) / (width - 1);
|
||||
dy = (ymax - ymin) / (height - 1);
|
||||
maxIterations = 10000;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
for (int row = startRow; row <= endRow; row++) {
|
||||
// Compute one row of pixels.
|
||||
ImageRow imageRow = calculateRow(row);
|
||||
// Check for the signal to immediately abort the computation.
|
||||
if (terminate || imageRow == null) return;
|
||||
// notify the listener about the processed image row
|
||||
processorListener.rowProcessed(imageRow);
|
||||
}
|
||||
} finally {
|
||||
// Make sure this is called when the task finishes for any reason.
|
||||
taskFinished(); // notify task completion; calls stopProcessing() when last tasks completed
|
||||
}
|
||||
}
|
||||
|
||||
private ImageRow calculateRow(int row) {
|
||||
final ImageRow imageRow = new ImageRow(row, width);
|
||||
double x;
|
||||
double y = ymax - dy * row;
|
||||
|
||||
for (int col = 0; col < width; col++) {
|
||||
x = xmin + dx * col;
|
||||
int count = 0;
|
||||
double xx = x;
|
||||
double yy = y;
|
||||
while (count < maxIterations && (xx * xx + yy * yy) < 4) {
|
||||
count++;
|
||||
double newxx = xx * xx - yy * yy + x;
|
||||
yy = 2 * xx * yy + y;
|
||||
xx = newxx;
|
||||
}
|
||||
// select color based on count of iterations
|
||||
imageRow.pixels[col] = (count != maxIterations) ?
|
||||
palette[count % palette.length] : Color.BLACK;
|
||||
// Check for the signal to immediately abort the computation.
|
||||
if (terminate) return null;
|
||||
}
|
||||
return imageRow;
|
||||
}
|
||||
} // end MandelbrotTask
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package ch.zhaw.prog2.mandelbrot;
|
||||
|
||||
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotCallableProcessor;
|
||||
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotExecutorProcessor;
|
||||
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotProcessor;
|
||||
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotTaskProcessor;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MandelbrotMockTest {
|
||||
private static final int width = 10;
|
||||
private static final int height = 5;
|
||||
|
||||
@Mock private MandelbrotProcessorListener listener;
|
||||
|
||||
private MandelbrotProcessorListener mandelbrotProcessorListenerTester = new MandelbrotProcessorListener() {
|
||||
@Override
|
||||
public void processingStopped(long duration) {
|
||||
System.out.println("Duration(ms): " + duration);
|
||||
assertNotEquals(0,duration, "Duration must not be 0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rowProcessed(ImageRow row) {
|
||||
assertNotNull(row);
|
||||
assertNotNull(row.pixels);
|
||||
assertEquals(width, row.pixels.length, "Length of Pixel-Array not matching");
|
||||
System.out.print("row-nr: " + row.rowNumber + " ");
|
||||
System.out.print("row-pixels: " + Arrays.stream(row.pixels)
|
||||
.map(Color::toString).collect(Collectors.joining(", ")));
|
||||
System.out.println();
|
||||
}
|
||||
};
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMandelbrotTaskProcessorListenerCalls() throws InterruptedException {
|
||||
MandelbrotProcessor processor = new MandelbrotTaskProcessor(listener, width, height);
|
||||
processor.startProcessing(1);
|
||||
Thread.sleep(2000);
|
||||
verify(listener, times(height)).rowProcessed(any(ImageRow.class));
|
||||
verify(listener, times(1)).processingStopped(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMandelbrotTaskProcessorListenerContent() throws InterruptedException {
|
||||
MandelbrotProcessor processor = new MandelbrotTaskProcessor(mandelbrotProcessorListenerTester, width, height);
|
||||
processor.startProcessing(1);
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMandelbrotExecutorProcessorListenerCalls() throws InterruptedException {
|
||||
MandelbrotProcessor processor = new MandelbrotExecutorProcessor(listener, width, height);
|
||||
processor.startProcessing(1);
|
||||
Thread.sleep(2000);
|
||||
verify(listener, times(height)).rowProcessed(any(ImageRow.class));
|
||||
verify(listener, times(1)).processingStopped(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMandelbrotExecutorProcessorListenerContent() throws InterruptedException {
|
||||
MandelbrotProcessor processor = new MandelbrotExecutorProcessor(mandelbrotProcessorListenerTester, width, height);
|
||||
processor.startProcessing(1);
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMandelbrotCallableProcessorListenerCalls() throws InterruptedException {
|
||||
MandelbrotProcessor processor = new MandelbrotCallableProcessor(listener, width, height);
|
||||
processor.startProcessing(1);
|
||||
Thread.sleep(2000);
|
||||
verify(listener, times(height)).rowProcessed(any(ImageRow.class));
|
||||
verify(listener, times(1)).processingStopped(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMandelbrotCallableProcessorListenerContent() throws InterruptedException {
|
||||
MandelbrotProcessor processor = new MandelbrotCallableProcessor(mandelbrotProcessorListenerTester, width, height);
|
||||
processor.startProcessing(1);
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user