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 <vladimir@thecybershadow.net>
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 		auto newPixels = countSort!`a.z`(pixels);
98 		delete pixels; pixels = newPixels; // avoid memory leak
99 
100 		canvas.clear(BGRX.init);
101 		foreach (ref pixel; pixels)
102 			canvas.safePut(
103 				canvas.w/2 + pixel.x,
104 				canvas.h/2 + pixel.y,
105 				pixel.color);
106 	}
107 
108 	override int run(string[] args)
109 	{
110 		if (args.length < 2)
111 			throw new Exception("No file specified - please specify a 24-bit .BMP file");
112 
113 		auto image = args[1].read().parseBMP!BGR();
114 
115 		//static bool havePixel[256][256][256];
116 		auto havePixel = new bool[256][256][256];
117 
118 		static bool extreme(uint b) { return b==0 || b==255; }
119 
120 		// Uncomment for bounding axes
121 		/*foreach (r; 0..256)
122 			foreach (g; 0..256)
123 				foreach (b; 0..256)
124 					havePixel[r][g][b] =
125 						//(r+g+b)%101 == 0 ||
126 						(extreme(r) && extreme(g)) ||
127 						(extreme(r) && extreme(b)) ||
128 						(extreme(g) && extreme(b));*/
129 
130 		foreach (y; 0..image.h)
131 			foreach (x; 0..image.w)
132 			{
133 				auto c = image[x, y];
134 				havePixel[c.r][c.g][c.b] = true;
135 			}
136 
137 		foreach (r; 0..256)
138 			foreach (g; 0..256)
139 				foreach (b; 0..256)
140 					if (havePixel[r][g][b])
141 						pixels ~= Pixel(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b);
142 
143 		shell = new SDL2Shell(this);
144 		shell.video = new SDL2SoftwareVideo();
145 		shell.run();
146 		shell.video.shutdown();
147 		return 0;
148 	}
149 
150 	override void handleKeyDown(Key key, dchar character)
151 	{
152 		switch (key)
153 		{
154 			case Key.up   : dy = -1; break;
155 			case Key.down : dy = +1; break;
156 			case Key.left : dx = -1; break;
157 			case Key.right: dx = +1; break;
158 			case Key.esc  : shell.quit(); break;
159 			default       : break;
160 		}
161 	}
162 
163 	override void handleKeyUp(Key key)
164 	{
165 		switch (key)
166 		{
167 			case Key.up   :
168 			case Key.down : dy = 0; break;
169 			case Key.left :
170 			case Key.right: dx = 0; break;
171 			default       : break;
172 		}
173 	}
174 
175 	override void handleQuit()
176 	{
177 		shell.quit();
178 	}
179 }
180 
181 shared static this()
182 {
183 	createApplication!MyApplication();
184 }