1 /**
2  * 3D visualizer of used colors in an image
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.demo.colorcube.colorcube;
15 
16 import std.datetime;
17 import std.file;
18 import std.math;
19 import std.parallelism;
20 
21 import ae.ui.app.application;
22 import ae.ui.app.main;
23 import ae.ui.shell.shell;
24 import ae.ui.shell.sdl2.shell;
25 import ae.ui.video.video;
26 import ae.ui.video.sdl2.video;
27 import ae.ui.video.renderer;
28 
29 import ae.utils.array;
30 import ae.utils.fps;
31 
32 import ae.utils.graphics.image;
33 import ae.utils.graphics.draw;
34 
35 final class MyApplication : Application
36 {
37 	override string getName() { return "Demo/ColorCube"; }
38 	override string getCompanyName() { return "CyberShadow"; }
39 
40 	Shell shell;
41 	FPSCounter fps;
42 
43 	int dx, dy;
44 	real ax=0, ay=0;
45 
46 	struct Pixel
47 	{
48 		BGRX color;
49 		int x0, y0, z0;
50 		int x, y, z;
51 
52 		this(ubyte r, ubyte g, ubyte b)
53 		{
54 			color = BGRX(b, g, r);
55 			x0 = r-128;
56 			y0 = g-128;
57 			z0 = b-128;
58 		}
59 
60 		void rotate(real sinx, real cosx, real siny, real cosy)
61 		{
62 			auto z1 = x0 *-siny + z0 * cosy;
63 			x = cast(int)(10000 + x0 * cosy + z0 * siny) - 10000; // hack: this is faster than lrint
64 			y = cast(int)(10000 + y0 * cosx - z1 * sinx) - 10000;
65 			z = cast(int)(10000 + y0 * sinx + z1 * cosx) - 10000;
66 		}
67 	}
68 
69 	Pixel[] pixels;
70 
71 	SysTime lastFrame;
72 
73 	/// Angular rotation speed (radians per second)
74 	enum RV = PI; // per second
75 
76 	override void render(Renderer s)
77 	{
78 		fps.tick(&shell.setCaption);
79 
80 		auto canvas = s.lock();
81 		scope(exit) s.unlock();
82 
83 		auto now = Clock.currTime();
84 		auto frameDuration = (now - lastFrame).total!"usecs" / 1_000_000f; // fractionary seconds
85 		lastFrame = now;
86 
87 		ay += dx*RV*frameDuration;
88 		ax += dy*RV*frameDuration;
89 
90 		auto sinx = sin(ax);
91 		auto cosx = cos(ax);
92 		auto siny = sin(ay);
93 		auto cosy = cos(ay);
94 
95 		foreach (ref pixel; parallel(pixels))
96 			pixel.rotate(sinx, cosx, siny, cosy);
97 		countSort!`a.z`(pixels);
98 
99 		canvas.clear(BGRX.init);
100 		foreach (ref pixel; pixels)
101 			canvas.safePut(
102 				canvas.w/2 + pixel.x,
103 				canvas.h/2 + pixel.y,
104 				pixel.color);
105 	}
106 
107 	override int run(string[] args)
108 	{
109 		if (args.length < 2)
110 			throw new Exception("No file specified - please specify a 24-bit .BMP file");
111 
112 		auto image = args[1].read().parseBMP!BGR();
113 
114 		//static bool havePixel[256][256][256];
115 		auto havePixel = new bool[256][256][256];
116 
117 		static bool extreme(uint b) { return b==0 || b==255; }
118 
119 		// Uncomment for bounding axes
120 		/*foreach (r; 0..256)
121 			foreach (g; 0..256)
122 				foreach (b; 0..256)
123 					havePixel[r][g][b] =
124 						//(r+g+b)%101 == 0 ||
125 						(extreme(r) && extreme(g)) ||
126 						(extreme(r) && extreme(b)) ||
127 						(extreme(g) && extreme(b));*/
128 
129 		foreach (y; 0..image.h)
130 			foreach (x; 0..image.w)
131 			{
132 				auto c = image[x, y];
133 				havePixel[c.r][c.g][c.b] = true;
134 			}
135 
136 		foreach (r; 0..256)
137 			foreach (g; 0..256)
138 				foreach (b; 0..256)
139 					if (havePixel[r][g][b])
140 						pixels ~= Pixel(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b);
141 
142 		shell = new SDL2Shell(this);
143 		shell.video = new SDL2SoftwareVideo();
144 		shell.run();
145 		shell.video.shutdown();
146 		return 0;
147 	}
148 
149 	override void handleKeyDown(Key key, dchar character)
150 	{
151 		switch (key)
152 		{
153 			case Key.up   : dy = -1; break;
154 			case Key.down : dy = +1; break;
155 			case Key.left : dx = -1; break;
156 			case Key.right: dx = +1; break;
157 			case Key.esc  : shell.quit(); break;
158 			default       : break;
159 		}
160 	}
161 
162 	override void handleKeyUp(Key key)
163 	{
164 		switch (key)
165 		{
166 			case Key.up   :
167 			case Key.down : dy = 0; break;
168 			case Key.left :
169 			case Key.right: dx = 0; break;
170 			default       : break;
171 		}
172 	}
173 
174 	override void handleQuit()
175 	{
176 		shell.quit();
177 	}
178 }
179 
180 shared static this()
181 {
182 	createApplication!MyApplication();
183 }