1 /**
2  * ImageMagick "convert" program wrapper
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.utils.graphics.im_convert;
15 
16 import std.exception;
17 import std.stdio;
18 import std.traits : ReturnType;
19 import std.typecons;
20 
21 import ae.sys.cmd;
22 import ae.sys.file;
23 import ae.sys.imagemagick;
24 import ae.utils.graphics.bitmap;
25 import ae.utils.graphics.color;
26 import ae.utils.graphics.image;
27 
28 /// Invoke ImageMagick's `convert` program to parse the given data.
29 auto parseViaIMConvert(COLOR)(const(void)[] data)
30 {
31 	string[] convertFlags;
32 	static if (is(COLOR : BGR))
33 	{
34 	//	convertFlags ~= ["-colorspace", "rgb"];
35 	//	convertFlags ~= ["-depth", "24"];
36 		convertFlags ~= ["-type", "TrueColor"];
37 		convertFlags ~= ["-alpha", "off"];
38 	}
39 	else
40 	static if (is(COLOR : BGRA))
41 	{
42 		convertFlags ~= ["-type", "TrueColorAlpha"];
43 		convertFlags ~= ["-alpha", "on"];
44 	}
45 	return data
46 		.pipe(["convert".imageMagickBinary()] ~ convertFlags ~ ["-[0]", "bmp:-"])
47 		.viewBMP!COLOR();
48 }
49 
50 /// ditto
51 auto parseViaIMConvert(C = TargetColor, TARGET)(const(void)[] data, auto ref TARGET target)
52 	if (isWritableView!TARGET && isTargetColor!(C, TARGET))
53 {
54 	return data.parseViaIMConvert!(ViewColor!TARGET)().copy(target);
55 }
56 
57 unittest
58 {
59 	if (false)
60 	{
61 		void[] data;
62 		parseViaIMConvert!BGR(data);
63 
64 		Image!BGR i;
65 		parseViaIMConvert!BGR(data, i);
66 	}
67 }
68 
69 // ----------------------------------------------------------------------------
70 
71 private struct DecodeStreamImpl(COLOR)
72 {
73 	alias BMP = ReturnType!(viewBMP!(COLOR, ubyte[]));
74 	@property BMP front() return
75 	{
76 		return frameBuf.viewBMP!COLOR();
77 	}
78 
79 	@property bool empty() { return done; }
80 
81 	void popFront()
82 	{
83 		auto headerBuf = frameBuf[0..Header.sizeof];
84 		if (!output.readExactly(headerBuf))
85 		{
86 			done = true;
87 			return;
88 		}
89 
90 		auto pHeader = cast(Header*)headerBuf.ptr;
91 		frameBuf.length = pHeader.bfSize;
92 		auto dataBuf = frameBuf[Header.sizeof..$];
93 		enforce(output.readExactly(dataBuf), "Unexpected end of stream");
94 	}
95 
96 	@disable this(this);
97 
98 	~this()
99 	{
100 		if (done)
101 			wait(pid);
102 		else
103 		{
104 			if (!tryWait(pid).terminated)
105 			{
106 				try
107 					kill(pid);
108 				catch (ProcessException e)
109 				{}
110 			}
111 
112 			version(Posix)
113 			{
114 				import core.sys.posix.signal : SIGKILL;
115 				if (!tryWait(pid).terminated)
116 				{
117 					try
118 						kill(pid, SIGKILL);
119 					catch (ProcessException e)
120 					{}
121 				}
122 			}
123 
124 			wait(pid);
125 		}
126 	}
127 
128 	private void initialize(File f, string fn)
129 	{
130 		auto pipes = pipe();
131 		output = pipes.readEnd();
132 
133 		string[] convertFlags;
134 		static if (is(COLOR : BGR))
135 		{
136 		//	convertFlags ~= ["-colorspace", "rgb"];
137 		//	convertFlags ~= ["-depth", "24"];
138 			convertFlags ~= ["-type", "TrueColor"];
139 			convertFlags ~= ["-alpha", "off"];
140 		}
141 		else
142 		static if (is(COLOR : BGRA))
143 		{
144 			convertFlags ~= ["-type", "TrueColorAlpha"];
145 			convertFlags ~= ["-alpha", "on"];
146 		}
147 		else
148 			static assert(false, "Unsupported color: " ~ COLOR.stringof);
149 
150 		auto args = [
151 			"convert".imageMagickBinary(),
152 			] ~ convertFlags ~ [
153 			fn,
154 			"bmp:-",
155 		];
156 		pid = spawnProcess(args, f, pipes.writeEnd);
157 
158 		frameBuf.length = Header.sizeof;
159 
160 		popFront();
161 	}
162 
163 private:
164 	import std.process;
165 
166 	Pid pid;
167 	File output;
168 	bool done;
169 
170 	alias BitmapHeader!3 Header;
171 	ubyte[] frameBuf;
172 }
173 
174 /// Represents a stream as a D range of frames.
175 struct DecodeStream(COLOR)
176 {
177 	private RefCounted!(DecodeStreamImpl!COLOR) impl;
178 	this(File f) { impl.initialize(f, "-"); } ///
179 	this(string fn) { impl.initialize(stdin, fn); } ///
180 	@property typeof(impl).BMP front() return { return impl.front; } ///
181 	@property bool empty() { return impl.empty; } ///
182 	void popFront() { impl.popFront(); } ///
183 }
184 //alias RefCounted!VideoStreamImpl VideoStream;
185 deprecated alias VideoStream = DecodeStream;
186 
187 /// Creates a `DecodeStream` from the given file,
188 /// providing a forward range of frames in the file.
189 DecodeStream!COLOR streamViaIMConvert(COLOR)(File f) { return DecodeStream!COLOR(f); }
190 DecodeStream!COLOR streamViaIMConvert(COLOR)(string fn) { return DecodeStream!COLOR(fn); } /// ditto
191 
192 unittest
193 {
194 	if (false)
195 	{
196 		streamViaIMConvert!BGR("");
197 		streamViaIMConvert!BGR(stdin);
198 	}
199 }