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, string[] transformations = null)
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]"] ~ transformations ~ ["bmp:-"])
47 		.viewBMP!COLOR();
48 }
49 
50 /// ditto
51 auto parseViaIMConvert(C = TargetColor, TARGET)(const(void)[] data, auto ref TARGET target, string[] transformations = null)
52 	if (isWritableView!TARGET && isTargetColor!(C, TARGET))
53 {
54 	return data.parseViaIMConvert!(ViewColor!TARGET)(transformations).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 /// Invoke ImageMagick's `convert` program to serialize the given image.
72 auto encodeViaIMConvert(SRC)(auto ref SRC src, string format, string[] transformations = null)
73 {
74 	string[] convertFlags;
75 	return src
76 		.toBMP
77 		.pipe(["convert".imageMagickBinary()] ~ convertFlags ~ ["-[0]"] ~ transformations ~ [format ~ ":-"]);
78 }
79 
80 unittest
81 {
82 	if (false)
83 	{
84 		Image!BGR i;
85 		i.encodeViaIMConvert("bmp");
86 	}
87 }
88 
89 // ----------------------------------------------------------------------------
90 
91 private struct DecodeStreamImpl(COLOR)
92 {
93 	alias BMP = ReturnType!(viewBMP!(COLOR, ubyte[]));
94 	@property BMP front() return
95 	{
96 		return frameBuf.viewBMP!COLOR();
97 	}
98 
99 	@property bool empty() { return done; }
100 
101 	void popFront()
102 	{
103 		auto headerBuf = frameBuf[0..Header.sizeof];
104 		if (!output.readExactly(headerBuf))
105 		{
106 			done = true;
107 			return;
108 		}
109 
110 		auto pHeader = cast(Header*)headerBuf.ptr;
111 		frameBuf.length = pHeader.bfSize;
112 		auto dataBuf = frameBuf[Header.sizeof..$];
113 		enforce(output.readExactly(dataBuf), "Unexpected end of stream");
114 	}
115 
116 	@disable this(this);
117 
118 	~this()
119 	{
120 		if (done)
121 			wait(pid);
122 		else
123 		{
124 			if (!tryWait(pid).terminated)
125 			{
126 				try
127 					kill(pid);
128 				catch (ProcessException e)
129 				{}
130 			}
131 
132 			version(Posix)
133 			{
134 				import core.sys.posix.signal : SIGKILL;
135 				if (!tryWait(pid).terminated)
136 				{
137 					try
138 						kill(pid, SIGKILL);
139 					catch (ProcessException e)
140 					{}
141 				}
142 			}
143 
144 			wait(pid);
145 		}
146 	}
147 
148 	private void initialize(File f, string fn)
149 	{
150 		auto pipes = pipe();
151 		output = pipes.readEnd();
152 
153 		string[] convertFlags;
154 		static if (is(COLOR : BGR))
155 		{
156 		//	convertFlags ~= ["-colorspace", "rgb"];
157 		//	convertFlags ~= ["-depth", "24"];
158 			convertFlags ~= ["-type", "TrueColor"];
159 			convertFlags ~= ["-alpha", "off"];
160 		}
161 		else
162 		static if (is(COLOR : BGRA))
163 		{
164 			convertFlags ~= ["-type", "TrueColorAlpha"];
165 			convertFlags ~= ["-alpha", "on"];
166 		}
167 		else
168 			static assert(false, "Unsupported color: " ~ COLOR.stringof);
169 
170 		auto args = [
171 			"convert".imageMagickBinary(),
172 			] ~ convertFlags ~ [
173 			fn,
174 			"bmp:-",
175 		];
176 		pid = spawnProcess(args, f, pipes.writeEnd);
177 
178 		frameBuf.length = Header.sizeof;
179 
180 		popFront();
181 	}
182 
183 private:
184 	import std.process;
185 
186 	Pid pid;
187 	File output;
188 	bool done;
189 
190 	alias BitmapHeader!3 Header;
191 	ubyte[] frameBuf;
192 }
193 
194 /// Represents a stream as a D range of frames.
195 struct DecodeStream(COLOR)
196 {
197 	private RefCounted!(DecodeStreamImpl!COLOR) impl;
198 	this(File f) { impl.initialize(f, "-"); } ///
199 	this(string fn) { impl.initialize(stdin, fn); } ///
200 	@property typeof(impl).BMP front() return { return impl.front; } ///
201 	@property bool empty() { return impl.empty; } ///
202 	void popFront() { impl.popFront(); } ///
203 }
204 //alias RefCounted!VideoStreamImpl VideoStream;
205 deprecated alias VideoStream = DecodeStream;
206 
207 /// Creates a `DecodeStream` from the given file,
208 /// providing a forward range of frames in the file.
209 DecodeStream!COLOR streamViaIMConvert(COLOR)(File f) { return DecodeStream!COLOR(f); }
210 DecodeStream!COLOR streamViaIMConvert(COLOR)(string fn) { return DecodeStream!COLOR(fn); } /// ditto
211 
212 unittest
213 {
214 	if (false)
215 	{
216 		streamViaIMConvert!BGR("");
217 		streamViaIMConvert!BGR(stdin);
218 	}
219 }