xref: /haiku/src/apps/mandelbrot/Mandelbrot.cpp (revision 495060760727dd782c9f8a90db71e5d727f19748)
1 /*
2  * Copyright 2016, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Augustin Cavalier <waddlesplash>
7  *		kerwizzy
8  */
9 
10 
11 #include <AboutWindow.h>
12 #include <Application.h>
13 #include <Bitmap.h>
14 #include <Catalog.h>
15 #include <MenuBar.h>
16 #include <LayoutBuilder.h>
17 #include <View.h>
18 #include <Window.h>
19 
20 #include <algorithm>
21 
22 #include "FractalEngine.h"
23 
24 #undef B_TRANSLATION_CONTEXT
25 #define B_TRANSLATION_CONTEXT "MandelbrotWindow"
26 
27 #define MANDELBROT_VIEW_REFRESH_FPS 10
28 
29 // #pragma mark - FractalView
30 
31 //#define TRACE_MANDELBROT_VIEW
32 #ifdef TRACE_MANDELBROT_VIEW
33 #	include <stdio.h>
34 #	define TRACE(x...) printf(x)
35 #else
36 #	define TRACE(x...)
37 #endif
38 
39 
40 class FractalView : public BView {
41 public:
42 	FractalView();
43 	~FractalView();
44 
45 	virtual void AttachedToWindow();
46 	virtual void FrameResized(float, float);
47 	virtual void Pulse();
48 
49 	virtual void MouseDown(BPoint where);
50 	virtual void MouseMoved(BPoint where, uint32 mode, const BMessage*);
51 	virtual void MouseUp(BPoint where);
52 
53 	virtual void MessageReceived(BMessage* msg);
54 	virtual void Draw(BRect updateRect);
55 
56 			void ResetPosition();
57 			void SetLocationFromFrame(double frameX, double frameY);
58 			void ZoomFractal(double originX, double originY, double zoomFactor);
59 			void ZoomFractalFromFrame(double frameOriginX, double frameOriginY,
60 				double zoomFactor);
61 			void ImportBitsAndInvalidate();
62 			void RedrawFractal();
63 			void UpdateSize();
64 			void CreateDisplayBitmap(uint16 width, uint16 height);
65 			FractalEngine* fFractalEngine;
66 
67 private:
68 			BRect GetDragFrame();
69 
70 	BPoint fSelectStart;
71 	BPoint fSelectEnd;
72 	bool fSelecting;
73 	uint32 fMouseButtons;
74 
75 	BBitmap* fDisplayBitmap;
76 
77 	double fLocationX;
78 	double fLocationY;
79 	double fSize;
80 };
81 
82 
83 FractalView::FractalView()
84 	:
85 	BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED),
86 	fFractalEngine(NULL),
87 	fSelecting(false),
88 	fDisplayBitmap(NULL),
89 	fLocationX(0),
90 	fLocationY(0),
91 	fSize(0.005)
92 {
93 	SetHighColor(make_color(255, 255, 255, 255));
94 }
95 
96 
97 FractalView::~FractalView()
98 {
99 	delete fDisplayBitmap;
100 }
101 
102 
103 void FractalView::ResetPosition()
104 {
105 	fLocationX = 0;
106 	fLocationY = 0;
107 	fSize = 0.005;
108 }
109 
110 
111 void FractalView::AttachedToWindow()
112 {
113 	fFractalEngine = new FractalEngine(this, Window());
114 	fFractalEngine->Run();
115 	TRACE("Attached to window\n");
116 }
117 
118 
119 void FractalView::FrameResized(float, float)
120 {
121 	TRACE("Frame Resize\n");
122 	UpdateSize();
123 }
124 
125 
126 void FractalView::UpdateSize()
127 {
128 	TRACE("Update Size\n");
129 	BMessage msg(FractalEngine::MSG_RESIZE);
130 
131 	uint16 width = (uint16)Frame().Width()+1;
132 	uint16 height = (uint16)Frame().Height()+1;
133 
134 	msg.AddUInt16("width", width);
135 	msg.AddUInt16("height", height);
136 
137 	CreateDisplayBitmap(width,height);
138 
139 	msg.AddPointer("bitmap",fDisplayBitmap);
140 
141 	fFractalEngine->PostMessage(&msg); // Create the new buffer
142 }
143 
144 
145 void FractalView::CreateDisplayBitmap(uint16 width,uint16 height)
146 {
147 	delete fDisplayBitmap;
148 	fDisplayBitmap = NULL;
149 	TRACE("width %u height %u\n",width,height);
150 	BRect rect(0, 0, width, height);
151 	fDisplayBitmap = new BBitmap(rect, B_RGB24);
152 }
153 
154 
155 BRect FractalView::GetDragFrame()
156 {
157 	BRect dragZone = BRect(std::min(fSelectStart.x, fSelectEnd.x),
158 		std::min(fSelectStart.y, fSelectEnd.y),
159 		std::max(fSelectStart.x, fSelectEnd.x),
160 		std::max(fSelectStart.y, fSelectEnd.y)),
161 		frame = Frame();
162 	float width = dragZone.Width(),
163 		height = width * (frame.Height() / frame.Width());
164 
165 	float x1 = fSelectStart.x, y1 = fSelectStart.y,	x2, y2;
166 	if (fSelectStart.x < fSelectEnd.x)
167 		x2 = x1 + width;
168 	else
169 		x2 = x1 - width;
170 	if (fSelectStart.y < fSelectEnd.y)
171 		y2 = y1 + height;
172 	else
173 		y2 = y1 - height;
174 	return BRect(x1, y1, x2, y2);
175 }
176 
177 
178 void FractalView::MouseDown(BPoint where)
179 {
180 	fSelecting = true;
181 	fSelectStart = where;
182 	fMouseButtons = 0;
183 	Window()->CurrentMessage()->FindInt32("buttons", (int32*)&fMouseButtons);
184 }
185 
186 
187 void FractalView::MouseMoved(BPoint where, uint32 mode, const BMessage*)
188 {
189 	if (fSelecting) {
190 		fSelectEnd = where;
191 		Invalidate();
192 	}
193 }
194 
195 
196 void FractalView::SetLocationFromFrame(double frameX,double frameY)
197 {
198 	BRect frame = Frame();
199 
200 	fLocationX = ((frameX - frame.Width() / 2) * fSize + fLocationX);
201 	fLocationY = ((frameY - frame.Height() / 2) * -fSize + fLocationY);
202 		// -fSize because is in raster coordinates (y swapped)
203 }
204 
205 
206 void FractalView::ZoomFractalFromFrame(double frameOriginX, double frameOriginY,
207 	double zoomFactor)
208 {
209 	BRect frame = Frame();
210 
211 	ZoomFractal((frameOriginX - frame.Width() / 2) * fSize + fLocationX,
212 		 (frameOriginY - frame.Height() / 2) * -fSize + fLocationY,
213 		 zoomFactor);
214 }
215 
216 
217 void FractalView::ZoomFractal(double originX, double originY, double zoomFactor)
218 {
219 	double deltaX = originX - fLocationX;
220 	double deltaY = originY - fLocationY;
221 
222 	TRACE("oX %g oY %g zoom %g\n", originX, originY, zoomFactor);
223 
224 	deltaX /= zoomFactor;
225 	deltaY /= zoomFactor;
226 
227 	fLocationX = originX - deltaX;
228 	fLocationY = originY - deltaY;
229 	fSize /= zoomFactor;
230 }
231 
232 
233 void FractalView::MouseUp(BPoint where)
234 {
235 	BRect frame = Frame();
236 	fSelecting = false;
237 	if (fabs(fSelectStart.x - where.x) > 4) {
238 		fSelectEnd = where;
239 		BRect dragFrame = GetDragFrame();
240 		BPoint lt = dragFrame.LeftTop();
241 		float centerX = lt.x + dragFrame.Width() / 2,
242 			centerY = lt.y + dragFrame.Height() / 2;
243 
244 		SetLocationFromFrame(centerX, centerY);
245 		fSize = std::fabs((dragFrame.Width() * fSize) / frame.Width());
246 	} else {
247 		if (fMouseButtons & B_PRIMARY_MOUSE_BUTTON) {
248 			SetLocationFromFrame(where.x, where.y);
249 			ZoomFractal(fLocationX, fLocationY, 2);
250 		} else {
251 			ZoomFractal(fLocationX, fLocationY, 0.5);
252 		}
253 	}
254 	RedrawFractal();
255 }
256 
257 
258 void FractalView::MessageReceived(BMessage* msg)
259 {
260 	switch (msg->what) {
261 	case B_MOUSE_WHEEL_CHANGED: {
262 		float change = msg->FindFloat("be:wheel_delta_y");
263 		BPoint where;
264 		GetMouse(&where, NULL);
265 		double zoomFactor;
266 		if (change < 0)
267 			zoomFactor = 3.0/2.0;
268 		else
269 			zoomFactor = 2.0/3.0;
270 		ZoomFractalFromFrame(where.x, where.y, zoomFactor);
271 
272 		RedrawFractal();
273 		break;
274 	}
275 
276 	case FractalEngine::MSG_BUFFER_CREATED:
277 		TRACE("Got buffer created msg.\n");
278 
279 		ImportBitsAndInvalidate();
280 		RedrawFractal();
281 		break;
282 
283 	case FractalEngine::MSG_RENDER_COMPLETE:
284 		TRACE("Got render complete msg.\n");
285 
286 		Window()->SetPulseRate(0);
287 		ImportBitsAndInvalidate();
288 		break;
289 
290 	default:
291 		BView::MessageReceived(msg);
292 		break;
293 	}
294 }
295 
296 
297 void FractalView::Pulse()
298 {
299 	ImportBitsAndInvalidate();
300 }
301 
302 
303 void FractalView::ImportBitsAndInvalidate()
304 {
305 	TRACE("Importing bits...\n");
306 
307 	fFractalEngine->WriteToBitmap(fDisplayBitmap);
308 	Invalidate();
309 }
310 
311 
312 void FractalView::RedrawFractal()
313 {
314 	Window()->SetPulseRate(1000000 / MANDELBROT_VIEW_REFRESH_FPS);
315 	BMessage message(FractalEngine::MSG_RENDER);
316 	message.AddDouble("locationX", fLocationX);
317 	message.AddDouble("locationY", fLocationY);
318 	message.AddDouble("size", fSize);
319 	fFractalEngine->PostMessage(&message);
320 }
321 
322 
323 void FractalView::Draw(BRect updateRect)
324 {
325 	DrawBitmap(fDisplayBitmap, updateRect, updateRect);
326 	if (fSelecting)
327 		StrokeRect(GetDragFrame());
328 }
329 
330 
331 // #pragma mark - MandelbrotWindow
332 
333 
334 class MandelbrotWindow : public BWindow
335 {
336 public:
337 	enum {
338 		MSG_MANDELBROT_SET = 'MndW',
339 		MSG_BURNINGSHIP_SET,
340 		MSG_TRICORN_SET,
341 		MSG_JULIA_SET,
342 		MSG_ORBITTRAP_SET,
343 		MSG_MULTIBROT_SET,
344 
345 		MSG_ROYAL_PALETTE,
346 		MSG_DEEPFROST_PALETTE,
347 		MSG_FROST_PALETTE,
348 		MSG_FIRE_PALETTE,
349 		MSG_MIDNIGHT_PALETTE,
350 		MSG_GRASSLAND_PALETTE,
351 		MSG_LIGHTNING_PALETTE,
352 		MSG_SPRING_PALETTE,
353 		MSG_HIGHCONTRAST_PALETTE,
354 
355 		MSG_ITER_128,
356 		MSG_ITER_512,
357 		MSG_ITER_1024,
358 		MSG_ITER_4096,
359 		MSG_ITER_8192,
360 		MSG_ITER_12288,
361 		MSG_ITER_16384,
362 
363 		MSG_SUBSAMPLING_1,
364 		MSG_SUBSAMPLING_2,
365 		MSG_SUBSAMPLING_3,
366 		MSG_SUBSAMPLING_4
367 	};
368 				MandelbrotWindow(BRect frame);
369 				~MandelbrotWindow() {}
370 
371 	virtual void MessageReceived(BMessage* msg);
372 	virtual bool QuitRequested();
373 
374 private:
375 		FractalView* fFractalView;
376 };
377 
378 
379 MandelbrotWindow::MandelbrotWindow(BRect frame)
380 	:
381 	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Mandelbrot"), B_TITLED_WINDOW_LOOK,
382 		B_NORMAL_WINDOW_FEEL, 0L),
383 	fFractalView(new FractalView)
384 {
385 	BMenuBar* menuBar = new BMenuBar("MenuBar");
386 	BMenu* setMenu;
387 	BMenu* paletteMenu;
388 	BMenu* iterMenu;
389 	BMenu* subsamplingMenu;
390 	BLayoutBuilder::Menu<>(menuBar)
391 		.AddMenu(B_TRANSLATE("File"))
392 			.AddItem(B_TRANSLATE("About"), B_ABOUT_REQUESTED)
393 			.AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q')
394 		.End()
395 		.AddMenu(B_TRANSLATE("Set"))
396 			.GetMenu(setMenu)
397 			.AddItem(B_TRANSLATE("Mandelbrot"), MSG_MANDELBROT_SET)
398 			.AddItem(B_TRANSLATE("Burning Ship"), MSG_BURNINGSHIP_SET)
399 			.AddItem(B_TRANSLATE("Tricorn"), MSG_TRICORN_SET)
400 			.AddItem(B_TRANSLATE("Julia"), MSG_JULIA_SET)
401 			.AddItem(B_TRANSLATE("Orbit Trap"), MSG_ORBITTRAP_SET)
402 			.AddItem(B_TRANSLATE("Multibrot"), MSG_MULTIBROT_SET)
403 		.End()
404 		.AddMenu(B_TRANSLATE("Palette"))
405 			.GetMenu(paletteMenu)
406 			.AddItem(B_TRANSLATE("Royal"), MSG_ROYAL_PALETTE)
407 			.AddItem(B_TRANSLATE("Deepfrost"), MSG_DEEPFROST_PALETTE)
408 			.AddItem(B_TRANSLATE("Frost"), MSG_FROST_PALETTE)
409 			.AddItem(B_TRANSLATE("Fire"), MSG_FIRE_PALETTE)
410 			.AddItem(B_TRANSLATE("Midnight"), MSG_MIDNIGHT_PALETTE)
411 			.AddItem(B_TRANSLATE("Grassland"), MSG_GRASSLAND_PALETTE)
412 			.AddItem(B_TRANSLATE("Lightning"), MSG_LIGHTNING_PALETTE)
413 			.AddItem(B_TRANSLATE("Spring"), MSG_SPRING_PALETTE)
414 			.AddItem(B_TRANSLATE("High contrast"), MSG_HIGHCONTRAST_PALETTE)
415 		.End()
416 		.AddMenu(B_TRANSLATE("Iterations"))
417 			.GetMenu(iterMenu)
418 			.AddItem("128", MSG_ITER_128)
419 			.AddItem("512", MSG_ITER_512)
420 			.AddItem("1024", MSG_ITER_1024)
421 			.AddItem("4096", MSG_ITER_4096)
422 			.AddItem("8192", MSG_ITER_8192)
423 			.AddItem("12288", MSG_ITER_12288)
424 			.AddItem("16384", MSG_ITER_16384)
425 		.End()
426 		.AddMenu(B_TRANSLATE("Subsampling"))
427 			.GetMenu(subsamplingMenu)
428 			.AddItem(B_TRANSLATE("1 (none)"), MSG_SUBSAMPLING_1)
429 			.AddItem("4", MSG_SUBSAMPLING_2)
430 			.AddItem("9", MSG_SUBSAMPLING_3)
431 			.AddItem("16", MSG_SUBSAMPLING_4)
432 		.End()
433 	.End();
434 	setMenu->SetRadioMode(true);
435 	setMenu->FindItem(MSG_MANDELBROT_SET)->SetMarked(true);
436 	paletteMenu->SetRadioMode(true);
437 	paletteMenu->FindItem(MSG_ROYAL_PALETTE)->SetMarked(true);
438 	iterMenu->SetRadioMode(true);
439 	iterMenu->FindItem(MSG_ITER_1024)->SetMarked(true);
440 	subsamplingMenu->SetRadioMode(true);
441 	subsamplingMenu->FindItem(MSG_SUBSAMPLING_2)->SetMarked(true);
442 
443 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
444 		.SetInsets(0)
445 		.Add(menuBar)
446 		.Add(fFractalView)
447 	.End();
448 }
449 
450 
451 #define HANDLE_SET(uiwhat, id) \
452 	case uiwhat: { \
453 		BMessage msg(FractalEngine::MSG_CHANGE_SET); \
454 		msg.AddUInt8("set", id); \
455 		fFractalView->fFractalEngine->PostMessage(&msg); \
456 		fFractalView->ResetPosition(); \
457 		fFractalView->RedrawFractal(); \
458 		break; \
459 	}
460 #define HANDLE_PALETTE(uiwhat, id) \
461 	case uiwhat: { \
462 		BMessage msg(FractalEngine::MSG_SET_PALETTE); \
463 		msg.AddUInt8("palette", id); \
464 		fFractalView->fFractalEngine->PostMessage(&msg); \
465 		fFractalView->RedrawFractal(); \
466 		break; \
467 	}
468 #define HANDLE_ITER(uiwhat, id) \
469 	case uiwhat: { \
470 		BMessage msg(FractalEngine::MSG_SET_ITERATIONS); \
471 		msg.AddUInt16("iterations", id); \
472 		fFractalView->fFractalEngine->PostMessage(&msg); \
473 		fFractalView->RedrawFractal(); \
474 		break; \
475 	}
476 #define HANDLE_SUBSAMPLING(uiwhat, id) \
477 	case uiwhat: { \
478 		BMessage msg(FractalEngine::MSG_SET_SUBSAMPLING); \
479 		msg.AddUInt8("subsampling", id); \
480 		fFractalView->fFractalEngine->PostMessage(&msg); \
481 		fFractalView->RedrawFractal(); \
482 		break; \
483 	}
484 void
485 MandelbrotWindow::MessageReceived(BMessage* msg)
486 {
487 	switch (msg->what) {
488 	HANDLE_SET(MSG_MANDELBROT_SET, 0)
489 	HANDLE_SET(MSG_BURNINGSHIP_SET, 1)
490 	HANDLE_SET(MSG_TRICORN_SET, 2)
491 	HANDLE_SET(MSG_JULIA_SET, 3)
492 	HANDLE_SET(MSG_ORBITTRAP_SET, 4)
493 	HANDLE_SET(MSG_MULTIBROT_SET, 5)
494 
495 	HANDLE_PALETTE(MSG_ROYAL_PALETTE, 0)
496 	HANDLE_PALETTE(MSG_DEEPFROST_PALETTE, 1)
497 	HANDLE_PALETTE(MSG_FROST_PALETTE, 2)
498 	HANDLE_PALETTE(MSG_FIRE_PALETTE, 3)
499 	HANDLE_PALETTE(MSG_MIDNIGHT_PALETTE, 4)
500 	HANDLE_PALETTE(MSG_GRASSLAND_PALETTE, 5)
501 	HANDLE_PALETTE(MSG_LIGHTNING_PALETTE, 6)
502 	HANDLE_PALETTE(MSG_SPRING_PALETTE, 7)
503 	HANDLE_PALETTE(MSG_HIGHCONTRAST_PALETTE, 8)
504 
505 	HANDLE_ITER(MSG_ITER_128, 128)
506 	HANDLE_ITER(MSG_ITER_512, 512)
507 	HANDLE_ITER(MSG_ITER_1024, 1024)
508 	HANDLE_ITER(MSG_ITER_4096, 4096)
509 	HANDLE_ITER(MSG_ITER_8192, 8192)
510 	HANDLE_ITER(MSG_ITER_12288, 12288)
511 	HANDLE_ITER(MSG_ITER_16384, 16384)
512 
513 	HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_1, 1)
514 	HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_2, 2)
515 	HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_3, 3)
516 	HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_4, 4)
517 
518 	case B_ABOUT_REQUESTED: {
519 		BAboutWindow* wind = new BAboutWindow("Mandelbrot",
520 			"application/x-vnd.Haiku-Mandelbrot");
521 
522 		const char* authors[] = {
523 			"Augustin Cavalier <waddlesplash>",
524 			"kerwizzy",
525 			NULL
526 		};
527 		wind->AddCopyright(2016, "Haiku, Inc.");
528 		wind->AddAuthors(authors);
529 		wind->Show();
530 		break;
531 	}
532 
533 	default:
534 		BWindow::MessageReceived(msg);
535 		break;
536 	}
537 }
538 #undef HANDLE_SET
539 #undef HANDLE_PALETTE
540 #undef HANDLE_ITER
541 #undef HANDLE_SUBSAMPLING
542 
543 
544 bool
545 MandelbrotWindow::QuitRequested()
546 {
547 	if (BWindow::QuitRequested()) {
548 		be_app->PostMessage(B_QUIT_REQUESTED);
549 		return true;
550 	}
551 	return false;
552 }
553 
554 
555 // #pragma mark - MandelbrotApp
556 
557 
558 class MandelbrotApp : public BApplication
559 {
560 public:
561 				MandelbrotApp()
562 					: BApplication("application/x-vnd.Haiku-Mandelbrot") {}
563 
564 		void	ReadyToRun();
565 		bool	QuitRequested() { return true; }
566 };
567 
568 
569 void
570 MandelbrotApp::ReadyToRun()
571 {
572 	MandelbrotWindow* wind = new MandelbrotWindow(BRect(0, 0, 640, 480));
573 	wind->CenterOnScreen();
574 	wind->Show();
575 }
576 
577 
578 int
579 main(int argc, char* argv[])
580 {
581 	MandelbrotApp().Run();
582 	return 0;
583 }
584