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 }