From 867b6bef7a0926a1e896adbd05290af887d1a61c Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 29 Nov 2023 09:43:05 +0700 Subject: [PATCH] multi-threaded renderer --- src/main/java/edu/Main.java | 53 ------------------- src/main/java/edu/project4/ImageUtils.java | 12 ++--- .../project4/processor/GammaCorrection.java | 10 ++-- .../ConcurrentPixelTwoDimensionalArray.java | 3 -- .../project4/render/MultiThreadRenderer.java | 24 +++++---- .../edu/project4/render/RenderThread.java | 26 ++++----- .../java/edu/project4/render/Renderer.java | 2 +- .../project4/render/SingleThreadRenderer.java | 25 +++++---- .../transformation/LinearTransformation.java | 2 +- .../SinusoidalTransformation.java | 4 +- .../SphericalTransformation.java | 4 +- .../java/edu/project4/ImageUtilsTest.java | 42 +++++++++++++++ 12 files changed, 102 insertions(+), 105 deletions(-) delete mode 100644 src/main/java/edu/Main.java create mode 100644 src/test/java/edu/project4/ImageUtilsTest.java diff --git a/src/main/java/edu/Main.java b/src/main/java/edu/Main.java deleted file mode 100644 index 323f96f..0000000 --- a/src/main/java/edu/Main.java +++ /dev/null @@ -1,53 +0,0 @@ -package edu; - -import edu.project4.ImageFormat; -import edu.project4.ImageUtils; -import edu.project4.processor.GammaCorrection; -import edu.project4.processor.ImageProcessor; -import edu.project4.render.FractalFlameConfiguration; -import edu.project4.render.MultiThreadRenderer; -import edu.project4.render.Renderer; -import edu.project4.render.SingleThreadRenderer; -import edu.project4.transformation.DiskTransformation; -import edu.project4.transformation.SinusoidalTransformation; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Random; - -public final class Main { - private final static Logger LOGGER = LogManager.getLogger(); - static Random random = new Random(); - - private Main() { - } - - public static void main(String[] args) throws IOException { -// long time3 = System.nanoTime(); -// Renderer renderer1 = new SingleThreadRenderer(); -// var image1 = renderer1.render( -// new FractalFlameConfiguration(1920, 1080, 20000, 1000, 1), -// List.of(new SinusoidalTransformation()) -// ); -// ImageProcessor processor1 = new GammaCorrection(2.2); -// processor1.process(image1); -// ImageUtils.print(image1); -// long time4 = System.nanoTime() - time3; -// System.out.println("single-threaded time: " + ((time4) / 1000000) + " milisec"); - for (int i = 2; i < 6; i++) { - long time1 = System.nanoTime(); - Renderer renderer = new MultiThreadRenderer(i); - var image = renderer.render( - new FractalFlameConfiguration(1920, 1080, 20000, 1000, 1), - List.of(new SinusoidalTransformation()) - ); - ImageProcessor processor = new GammaCorrection(2.2); - processor.process(image); - ImageUtils.print(image); - long time2 = System.nanoTime() - time1; - System.out.println("multi-threaded time " + i + " threads: " + ((time2) / 1000000) + " milisec"); - } - } -} diff --git a/src/main/java/edu/project4/ImageUtils.java b/src/main/java/edu/project4/ImageUtils.java index acdfad2..866081b 100644 --- a/src/main/java/edu/project4/ImageUtils.java +++ b/src/main/java/edu/project4/ImageUtils.java @@ -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()); } diff --git a/src/main/java/edu/project4/processor/GammaCorrection.java b/src/main/java/edu/project4/processor/GammaCorrection.java index aed3431..2ec42dd 100644 --- a/src/main/java/edu/project4/processor/GammaCorrection.java +++ b/src/main/java/edu/project4/processor/GammaCorrection.java @@ -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; @@ -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++) { @@ -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))); diff --git a/src/main/java/edu/project4/render/ConcurrentPixelTwoDimensionalArray.java b/src/main/java/edu/project4/render/ConcurrentPixelTwoDimensionalArray.java index e11de43..c20db3b 100644 --- a/src/main/java/edu/project4/render/ConcurrentPixelTwoDimensionalArray.java +++ b/src/main/java/edu/project4/render/ConcurrentPixelTwoDimensionalArray.java @@ -1,8 +1,6 @@ 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 @@ -10,7 +8,6 @@ * Warning: 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) { diff --git a/src/main/java/edu/project4/render/MultiThreadRenderer.java b/src/main/java/edu/project4/render/MultiThreadRenderer.java index 0630c8b..ce9caba 100644 --- a/src/main/java/edu/project4/render/MultiThreadRenderer.java +++ b/src/main/java/edu/project4/render/MultiThreadRenderer.java @@ -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; @@ -24,18 +25,20 @@ public MultiThreadRenderer(int threadCount) { @Override public FractalImage render(FractalFlameConfiguration configuration, List 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); } @@ -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; } } diff --git a/src/main/java/edu/project4/render/RenderThread.java b/src/main/java/edu/project4/render/RenderThread.java index b13afbc..8170b0a 100644 --- a/src/main/java/edu/project4/render/RenderThread.java +++ b/src/main/java/edu/project4/render/RenderThread.java @@ -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; @@ -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; @@ -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); } } @@ -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) { diff --git a/src/main/java/edu/project4/render/Renderer.java b/src/main/java/edu/project4/render/Renderer.java index 3b9cdbc..022a87e 100644 --- a/src/main/java/edu/project4/render/Renderer.java +++ b/src/main/java/edu/project4/render/Renderer.java @@ -6,5 +6,5 @@ @FunctionalInterface public interface Renderer { - public FractalImage render(FractalFlameConfiguration configuration, List variations); + FractalImage render(FractalFlameConfiguration configuration, List variations); } diff --git a/src/main/java/edu/project4/render/SingleThreadRenderer.java b/src/main/java/edu/project4/render/SingleThreadRenderer.java index 9c45e03..4d6ddca 100644 --- a/src/main/java/edu/project4/render/SingleThreadRenderer.java +++ b/src/main/java/edu/project4/render/SingleThreadRenderer.java @@ -9,11 +9,14 @@ 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 variations) { @@ -21,11 +24,11 @@ public FractalImage render(FractalFlameConfiguration configuration, List