Skip to content

Commit

Permalink
feature: added option to change color of converged coordinates; fix: …
Browse files Browse the repository at this point in the history
…reset button now resets also color palette; changed placement of inputs on the page; added hexColorToRGB function
  • Loading branch information
Zazzik1 committed Aug 22, 2023
1 parent f97748f commit 88d794d
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 25 deletions.
14 changes: 11 additions & 3 deletions src/Mandelbrot.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { DEFAULT_COLOR_OFFSET, DEFAULT_RGB, DEFAULT_WORKERS_NO } from "~/constants";
import { MandelbrotMessageData, MandelbrotWorkerMessageData, RGBColorPalette, Task } from "~/types";
import { DEFAULT_COLOR_OFFSET, DEFAULT_CONVERGED_COLOR, DEFAULT_ITERATIONS, DEFAULT_RGB, DEFAULT_WORKERS_NO } from "~/constants";
import { IRGB, MandelbrotMessageData, MandelbrotWorkerMessageData, RGBColorPalette, Task } from "~/types";
import MandelbrotWorker from "~/workers/mandelbrot.worker";
import { hexColorToRGB } from "./utils/utils";

export default class Mandelbrot {
protected canvas: HTMLCanvasElement;
protected ctx: CanvasRenderingContext2D;
protected resolveDrawFn?: Function;
protected iterations: number = 120;
protected iterations: number = DEFAULT_ITERATIONS;
protected workersNo = DEFAULT_WORKERS_NO;
protected workersFinished: boolean[] = [];
protected workers: Worker[] = [];
protected rgb: RGBColorPalette = DEFAULT_RGB;
protected convergedColor: IRGB = DEFAULT_CONVERGED_COLOR;
protected colorOffset: number = DEFAULT_COLOR_OFFSET;
protected isRunning: boolean = false;

Expand Down Expand Up @@ -38,6 +40,11 @@ export default class Mandelbrot {
}
}

public setConvergedColor(color: IRGB | string) {
if (Array.isArray(color)) return this.convergedColor = color;
return this.convergedColor = hexColorToRGB(color);
}

public setColorOffset(colorOffset: number) {
this.colorOffset = colorOffset;
}
Expand Down Expand Up @@ -69,6 +76,7 @@ export default class Mandelbrot {
db: (y2 - y1) / height,
iterations: this.iterations,
colorOffset: this.colorOffset,
convergedColor: this.convergedColor,
}

const { workersNo } = this;
Expand Down
4 changes: 3 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RGBColorPalette } from "~/types";
import { IRGB, RGBColorPalette } from "~/types";

export const DEFAULT_RGB: RGBColorPalette = [
[66, 30, 15],
Expand Down Expand Up @@ -39,6 +39,8 @@ export const RGB_PALETTES: Record<string, RGBColorPalette> = {
GRAY_RGB,
}

export const DEFAULT_ITERATIONS: number = 120;
export const DEFAULT_CONVERGED_COLOR: IRGB = [0, 0, 0];
export const SUGGESTED_ITERATIONS = [5, 20, 40, 120, 200, 500, 800, 1000, 1200, 1400, 2000, 2500, 3000, 3500, 4000, 4500, 5000];
export const CANVAS_SIZES = [
{ name: '400x400', value: '400x400' },
Expand Down
32 changes: 21 additions & 11 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,19 @@
<div>
<label>
Max iterations (precision):
<input list="iterations" id="iter" value="120" />
<input list="iterations" id="iter" />
<input type="button" value="+25" id="iter+25" />
<input type="button" value="-25" id="iter-25" />
</label>
<br />
<input id="reset" type="button" value="Reset settings"/>
<datalist id="iterations"></datalist>
</div>
<div>
<label>
Canvas size:
<select id="cSize"></select>
</label>
<span>Larger canvas sizes as well as larger maximum iteration values ​​can take much longer to compute.</span><br>
<label>
Color offset:
<input id="color-offset" type="number" value="0" min="0" step="1"/>
<select id="color-palette"></select>
</label>
<span>Larger canvas sizes as well as larger maximum iteration values ​​can take much longer to compute.</span>
<br />
<input id="reset" type="button" value="Reset settings"/>
<datalist id="iterations"></datalist>
</div>
<div>
<input id="zoom_minus" type="button" value="Zoom -"/>
Expand All @@ -44,6 +38,22 @@
</label>
<span>If you want to zoom, just click on desired place on canvas and sometimes increase iterations to make edges sharpen.</span><br>
</div>
<div>
<label>
Color offset:
<input id="color-offset" type="number" value="0" min="0" step="1"/>
</label>
<br />
<label>
Palette:
<select id="color-palette"></select>
</label>
<br />
<label>
Converged color:
<input type="color" id="color-converged" />
</label>
</div>
<div>
<input id="download" type="button" value="Download image"/>
</div>
Expand Down
16 changes: 13 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Mandelbrot from "~/Mandelbrot";
import { CANVAS_SIZES, DOWNLOADED_FILE_NAME, RGB_PALETTES, SUGGESTED_ITERATIONS, ZOOM_MULTIPLIER } from "~/constants";
import { CANVAS_SIZES, DEFAULT_CONVERGED_COLOR, DEFAULT_ITERATIONS, DOWNLOADED_FILE_NAME, RGB_PALETTES, SUGGESTED_ITERATIONS, ZOOM_MULTIPLIER } from "~/constants";
import '~/styles/styles';
import StateManager from "~/utils/StateManager/StateManager";
import URLSearchParamsStrategy from "~/utils/StateManager/strategies/URLSearchParamsStrategy";
import { AppState } from "~/types";
import { AppState, RGBColorPalette } from "~/types";

const stateManager = new StateManager<AppState>(URLSearchParamsStrategy);
const iterationsDatalist = document.querySelector('#iterations') as HTMLSelectElement;
const colorPaletteSelectElement = document.querySelector("#color-palette") as HTMLSelectElement;
const colorConvergedInput = document.querySelector("#color-converged") as HTMLInputElement;
const canvas = document.querySelector("#canvas") as HTMLCanvasElement;
const wheel = document.querySelector("#wheel") as HTMLInputElement;
const mandelbrot = new Mandelbrot(canvas);
Expand Down Expand Up @@ -49,6 +50,8 @@ function draw() {
let y1 = input.get(INPUTS.Y1);
mandelbrot.setIterations(input.get(INPUTS.ITER));
mandelbrot.setColorOffset(input.get(INPUTS.COLOR_OFFSET));
mandelbrot.setColorPalette(RGB_PALETTES[colorPaletteSelectElement.value]);
mandelbrot.setConvergedColor(colorConvergedInput.value);
mandelbrot.draw(x1, y1, x1 + len, y1 + len2);
}

Expand Down Expand Up @@ -91,7 +94,7 @@ function updateState() {

function loadInitialStateFromURLParams() {
let { x1, x2, y1, y2, i, colorOffset } = stateManager.getState();
if (!i || Number.isNaN(+i)) i = '120';
if (!i || Number.isNaN(+i)) i = DEFAULT_ITERATIONS.toString();
if (!x1 || Number.isNaN(+x1)) x1 = '-2';
if (!x2 || Number.isNaN(+x2)) x2 = '1';
if (!y1 || Number.isNaN(+y1)) y1 = '-1.5';
Expand All @@ -104,6 +107,8 @@ function loadInitialStateFromURLParams() {
input.set(INPUTS.LEN2, y2 ? (+y2) - input.get(INPUTS.Y1) : 1.5);
input.set(INPUTS.COLOR_OFFSET, colorOffset);
input.set(INPUTS.ITER, i);
colorPaletteSelectElement.value = Object.keys(RGB_PALETTES)[0];
colorConvergedInput.value = `rgb(${DEFAULT_CONVERGED_COLOR.join(',')})`;
}

function initializeDatasetsAndSelects() {
Expand Down Expand Up @@ -185,6 +190,11 @@ function addListeners() {
mandelbrot.setColorPalette(RGB_PALETTES[colorPaletteSelectElement.value]);
draw();
});

colorConvergedInput.addEventListener("change", () => {
mandelbrot.setConvergedColor(colorConvergedInput.value);
draw();
});

INPUTS.CSIZE.addEventListener("change", () => {
let [width, height] = INPUTS.CSIZE.value.split("x");
Expand Down
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ export interface Task {
db: number;
iterations: number;
colorOffset: number,
convergedColor: IRGB,
}

export type RGBColorPalette = [number, number, number][];
export type IRGB = [number, number, number];
export type RGBColorPalette = IRGB[];

declare global {
interface Window { mandelbrot?: Mandelbrot }
Expand Down
16 changes: 14 additions & 2 deletions src/utils/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isInSet } from "./utils";
import { hexColorToRGB, isInSet } from "./utils";

describe('isInSet', () => {
const ITERATIONS = 80;
Expand All @@ -11,4 +11,16 @@ describe('isInSet', () => {
expect(isInSet(1, 0, ITERATIONS)).toBeTruthy();
expect(isInSet(4, 4, ITERATIONS)).toBeTruthy();
});
});
});

describe('hexColorToRGB', () => {
test('#ffffff is converted to [255, 255, 255]', () => {
expect(hexColorToRGB('#ffffff')).toEqual([255, 255, 255]);
});
test('#000000 is converted to [0, 0, 0]', () => {
expect(hexColorToRGB('#000000')).toEqual([0, 0, 0]);
});
test('#adbcb0 is converted to [173, 188, 176]', () => {
expect(hexColorToRGB('#adbcb0')).toEqual([173, 188, 176]);
});
})
11 changes: 10 additions & 1 deletion src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { IRGB } from "~/types";

/**
* Decides if the given point (a+bi) belongs to the Mandelbrot set.
*
Expand All @@ -15,4 +17,11 @@ export function isInSet(a: number, b: number, iterations: number) {
if (Math.sqrt(aa ** 2 + (bb ** 2)) > 2) return i // diverges
}
return 0;
}
}

export function hexColorToRGB(color: string): IRGB {
const r = parseInt(color.substr(1,2), 16);
const g = parseInt(color.substr(3,2), 16);
const b = parseInt(color.substr(5,2), 16);
return [r, g, b];
}
7 changes: 4 additions & 3 deletions src/workers/mandelbrot.worker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEFAULT_CONVERGED_COLOR } from "~/constants";
import { NotRunningError } from "~/errors";
import { CalculateActionPayload, MandelbrotMessageData, MandelbrotWorkerMessageData, RGBColorPalette, Task, WorkerData } from "~/types";
import { CalculateActionPayload, IRGB, MandelbrotMessageData, MandelbrotWorkerMessageData, RGBColorPalette, Task, WorkerData } from "~/types";
import { isInSet } from "~/utils/utils";

class MandelbrotWorker {
Expand Down Expand Up @@ -47,11 +48,11 @@ class MandelbrotWorker {
if (!this.data.isRunning) throw new NotRunningError();
const { x1, y1, da, db, iterations, width, colorOffset } = this.data.task;
const line = new ImageData(width, 1);
let c: [number, number, number];
let c: IRGB;
for(let x = 0; x < width*4; x+=4){
let diverge = isInSet(x1 + (x/4 * da), y1 + (y * db), iterations);
if (!diverge) {
c = [0, 0, 0]; // point belongs to the set
c = this.data.task.convergedColor; // point belongs to the set
} else {
let color = (diverge + colorOffset) % this.data.rgb.length;
c = this.data.rgb[color]; // colors outer points
Expand Down

0 comments on commit 88d794d

Please sign in to comment.