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 }