Skip to content

Commit

Permalink
multi-threaded renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander committed Nov 29, 2023
1 parent 699f00f commit 867b6be
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 105 deletions.
53 changes: 0 additions & 53 deletions src/main/java/edu/Main.java

This file was deleted.

12 changes: 5 additions & 7 deletions src/main/java/edu/project4/ImageUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ private ImageUtils() {

public static void save(FractalImage image, Path filename, ImageFormat format) throws IOException {
BufferedImage bufferedImage = convertFractalToBuffered(image);
String fileFormat;
switch (format) {
case ImageFormat.BMP -> fileFormat = "bmp";
case ImageFormat.JPEG -> fileFormat = "jpeg";
default -> fileFormat = "png";
}
filename = Path.of(String.format("%s.%s",filename.toString(), fileFormat));
String fileFormat = switch (format) {
case ImageFormat.BMP -> "bmp";
case ImageFormat.JPEG -> "jpeg";
default -> "png";
};
ImageIO.write(bufferedImage, fileFormat, filename.toFile());
}

Expand Down
10 changes: 6 additions & 4 deletions src/main/java/edu/project4/processor/GammaCorrection.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import static java.lang.Math.log10;
import static java.lang.Math.pow;

public class GammaCorrection implements ImageProcessor{
public class GammaCorrection implements ImageProcessor {
private final double gamma;
private double maxNormal;

Expand All @@ -19,7 +19,8 @@ public void process(FractalImage image) {
setNormals(image);
changeBrightness(image);
}
private void setNormals(FractalImage image){

private void setNormals(FractalImage image) {
var pixels = image.pixels();
for (int row = 0; row < image.height(); row++) {
for (int col = 0; col < image.width(); col++) {
Expand All @@ -34,12 +35,13 @@ private void setNormals(FractalImage image){
}
}
}
private void changeBrightness(FractalImage image){

private void changeBrightness(FractalImage image) {
var pixels = image.pixels();
for (int row = 0; row < image.height(); row++) {
for (int col = 0; col < image.width(); col++) {
Pixel pixel = pixels[row][col];
if(pixel.hitCount() != 0){
if (pixel.hitCount() != 0) {
double normal = pixel.normal() / maxNormal;
int r = (int) (pixel.r() * pow(normal, (1.0 / gamma)));
int g = (int) (pixel.g() * pow(normal, (1.0 / gamma)));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package edu.project4.render;

import edu.project4.Pixel;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* Two-dimensional array of objects
* <p>
* <strong>Warning:</strong> The number of columns must be the same
*/
public class ConcurrentPixelTwoDimensionalArray {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Pixel[][] array;

public ConcurrentPixelTwoDimensionalArray(int rows, int columns) {
Expand Down
24 changes: 14 additions & 10 deletions src/main/java/edu/project4/render/MultiThreadRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
import edu.project4.FractalImage;
import edu.project4.Pixel;
import edu.project4.transformation.Transformation;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class MultiThreadRenderer implements Renderer {
private static final Random random = new Random();
private final static Random RANDOM = new Random();
private final static int AFFINE_FACTORS = 5;
private final static int MAX_WAITING_MINUTES_FOR_RENDERER = 3;

private final int threadCount;
private int threadCount;

public MultiThreadRenderer(int threadCount) {
this.threadCount = threadCount;
Expand All @@ -24,18 +25,20 @@ public MultiThreadRenderer(int threadCount) {
@Override
public FractalImage render(FractalFlameConfiguration configuration, List<Transformation> variations) {
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
Transformation transformation = variations.get(random.nextInt(0, variations.size()));
Transformation transformation = variations.get(RANDOM.nextInt(0, variations.size()));
int xResolution = configuration.width();
int yResolution = configuration.height();
var pixels = createInitialPixels(xResolution, yResolution);
var affineFactors = createFactors(5);
var affineFactors = createFactors(AFFINE_FACTORS);
AtomicInteger sample = new AtomicInteger(0);
for (int i = 0; i < threadCount; i++) {
executorService.execute(new RenderThread(configuration, affineFactors, transformation, pixels, sample));
}
executorService.shutdown();
while (!executorService.isTerminated()) {

try {
executorService.awaitTermination(MAX_WAITING_MINUTES_FOR_RENDERER, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return new FractalImage(pixels.toArray(), xResolution, yResolution);
}
Expand Down Expand Up @@ -69,10 +72,11 @@ private ConcurrentPixelTwoDimensionalArray createInitialPixels(int width, int he
}

private double randomFactor() {
return random.nextDouble(-1, 1);
return RANDOM.nextDouble(-1, 1);
}

@SuppressWarnings("MagicNumber")
private int randomColor() {
return random.nextInt(64, 256) + 64;
return RANDOM.nextInt(64, 256) + 64;
}
}
26 changes: 14 additions & 12 deletions src/main/java/edu/project4/render/RenderThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@

public class RenderThread implements Runnable {

private static final Point POINT_MIN = new Point(-1.777, -1.0);
private static final Point POINT_MAX = new Point(1.777, 1.0);
private static final double RANGE_X = POINT_MAX.x() - POINT_MIN.x();
private static final double RANGE_Y = POINT_MAX.y() - POINT_MIN.y();
private static final Random random = new Random();
private final static Point POINT_MIN = new Point(-1.777, -1.0);
private final static Point POINT_MAX = new Point(1.777, 1.0);
private final static double RANGE_X = POINT_MAX.x() - POINT_MIN.x();
private final static double RANGE_Y = POINT_MAX.y() - POINT_MIN.y();
private final static Random RANDOM = new Random();
private final static int SKIP_ITERATIONS = 20;

private final FractalFlameConfiguration configuration;
private final AffineFactorContainer[] affineFactors;
Expand Down Expand Up @@ -42,8 +43,8 @@ public void run() {
for (; sample.get() < configuration.samples(); sample.incrementAndGet()) {
Point next = randomPoint();

for (int iter = -20; iter < configuration.iterPerSample(); iter++) {
int iterAffineFactors = random.nextInt(0, affineFactors.length);
for (int iter = -SKIP_ITERATIONS; iter < configuration.iterPerSample(); iter++) {
int iterAffineFactors = RANDOM.nextInt(0, affineFactors.length);
Point linear = computeLinear(affineFactors[iterAffineFactors], next);
Point nonLinear = transformation.apply(linear);
next = nonLinear;
Expand All @@ -58,7 +59,7 @@ public void run() {
int x1 = xResolution - (int) (((POINT_MAX.x() - rotated.x()) / (RANGE_X)) * xResolution);
int y1 = yResolution - (int) (((POINT_MAX.y() - rotated.y()) / (RANGE_Y)) * yResolution);
if (x1 < xResolution && y1 < yResolution) {
Pixel updatedPixel = getNewColor(pixels.get(y1,x1), affineFactors[iterAffineFactors]);
Pixel updatedPixel = getNewColor(pixels.get(y1, x1), affineFactors[iterAffineFactors]);
pixels.set(y1, x1, updatedPixel);
}
}
Expand All @@ -83,10 +84,11 @@ private Pixel getNewColor(Pixel pixel, AffineFactorContainer factors) {
}
return new Pixel(r, g, b, pixel.hitCount() + 1, 0);
}
private Point randomPoint(){
double newX = random.nextDouble(POINT_MIN.x(), POINT_MAX.x());
double newY = random.nextDouble(POINT_MIN.y(), POINT_MAX.y());
return new Point(newX,newY);

private Point randomPoint() {
double newX = RANDOM.nextDouble(POINT_MIN.x(), POINT_MAX.x());
double newY = RANDOM.nextDouble(POINT_MIN.y(), POINT_MAX.y());
return new Point(newX, newY);
}

private Point computeLinear(AffineFactorContainer factors, Point next) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/edu/project4/render/Renderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

@FunctionalInterface
public interface Renderer {
public FractalImage render(FractalFlameConfiguration configuration, List<Transformation> variations);
FractalImage render(FractalFlameConfiguration configuration, List<Transformation> variations);
}
25 changes: 15 additions & 10 deletions src/main/java/edu/project4/render/SingleThreadRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,26 @@
import java.util.Random;

public class SingleThreadRenderer implements Renderer {
private static final Random random = new Random();
private static final Point POINT_MIN = new Point(-1.777, -1.0);
private static final Point POINT_MAX = new Point(1.777, 1.0);
private static final double RANGE_X = POINT_MAX.x() - POINT_MIN.x();
private static final double RANGE_Y = POINT_MAX.y() - POINT_MIN.y();
private final static Point POINT_MIN = new Point(-1.777, -1.0);
private final static Point POINT_MAX = new Point(1.777, 1.0);
private final static double RANGE_X = POINT_MAX.x() - POINT_MIN.x();
private final static double RANGE_Y = POINT_MAX.y() - POINT_MIN.y();
private final static int SKIP_ITERATIONS = 20;
private final static int AFFINE_FACTORS = 5;

private final Random random = new Random();

@Override
public FractalImage render(FractalFlameConfiguration configuration, List<Transformation> variations) {
Transformation transformation = variations.get(random.nextInt(0, variations.size()));
int xResolution = configuration.width();
int yResolution = configuration.height();
var pixels = createInitialPixels(xResolution, yResolution);
var affineFactors = createFactors(5);
var affineFactors = createFactors(AFFINE_FACTORS);

for (int num = 0; num < configuration.samples(); num++) {
Point next = randomPoint();
for (int iter = -20; iter < configuration.iterPerSample(); iter++) {
for (int iter = -SKIP_ITERATIONS; iter < configuration.iterPerSample(); iter++) {
int iterAffineFactors = random.nextInt(0, affineFactors.length);
Point linear = computeLinear(affineFactors[iterAffineFactors], next);
Point nonLinear = transformation.apply(linear);
Expand All @@ -41,7 +44,7 @@ public FractalImage render(FractalFlameConfiguration configuration, List<Transfo
int x1 = xResolution - (int) (((POINT_MAX.x() - rotated.x()) / (RANGE_X)) * xResolution);
int y1 = yResolution - (int) (((POINT_MAX.y() - rotated.y()) / (RANGE_Y)) * yResolution);
if (x1 < xResolution && y1 < yResolution) {
pixels[y1][x1] = getNewColor(pixels[y1][x1],affineFactors[iterAffineFactors]);
pixels[y1][x1] = getNewColor(pixels[y1][x1], affineFactors[iterAffineFactors]);
}
}
}
Expand Down Expand Up @@ -100,10 +103,11 @@ private Point computeLinear(AffineFactorContainer factors, Point next) {
double y = factors.c() * next.x() + factors.d() * next.y() + factors.f();
return new Point(x, y);
}
private Point randomPoint(){

private Point randomPoint() {
double newX = random.nextDouble(POINT_MIN.x(), POINT_MAX.x());
double newY = random.nextDouble(POINT_MIN.y(), POINT_MAX.y());
return new Point(newX,newY);
return new Point(newX, newY);
}

private Point rotate(Point nonLinear, double theta) {
Expand All @@ -116,6 +120,7 @@ private double randomFactor() {
return random.nextDouble(-1, 1);
}

@SuppressWarnings("MagicNumber")
private int randomColor() {
return random.nextInt(64, 256) + 64;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import edu.project4.Point;

public class LinearTransformation implements Transformation{
public class LinearTransformation implements Transformation {
@Override
public Point apply(Point point) {
return point;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import edu.project4.Point;

public class SinusoidalTransformation implements Transformation{
public class SinusoidalTransformation implements Transformation {
@Override
public Point apply(Point point) {
double newX = Math.sin(point.x());
double newY = Math.sin(point.y());
return new Point(newX,newY);
return new Point(newX, newY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import edu.project4.Point;

public class SphericalTransformation implements Transformation{
public class SphericalTransformation implements Transformation {
@Override
public Point apply(Point point) {
double radius = 1.0 / (point.x() * point.x() + point.y() * point.y());
double newX = radius * point.x();
double newY = radius * point.y();
return new Point(newX,newY);
return new Point(newX, newY);
}
}
42 changes: 42 additions & 0 deletions src/test/java/edu/project4/ImageUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package edu.project4;

import java.io.IOException;
import java.nio.file.Path;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

public class ImageUtilsTest {
private static final Path IMAGE = Path.of("src/test/java/edu/project4/image.png");

@Test
@DisplayName("Сохранение изображения")
public void save_shouldCreateImage() {
Pixel[][] pixels = new Pixel[10][10];
for (int i = 0; i < pixels.length; i++) {
for (int j = 0; j < pixels[i].length; j++) {
pixels[i][j] = new Pixel(0, 0, 0, 0, 0);
}
}

try {
ImageUtils.save(
new FractalImage(pixels, 10, 10),
IMAGE,
ImageFormat.PNG
);
} catch (IOException e) {
throw new RuntimeException(e);
}

assertThat(IMAGE.toFile().exists()).isTrue();
}

@AfterAll
public static void after() {
if (IMAGE.toFile().exists()) {
IMAGE.toFile().delete();
}
}
}

0 comments on commit 867b6be

Please sign in to comment.