Advertisement
Guest User

Untitled

a guest
Nov 27th, 2015
65
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.94 KB | None | 0 0
  1. # Use NPM
  2.  
  3. ElectronはNodeとほとんど差がないので、NPMで入れたパッケージも使える。
  4. 今回は簡単な画像編集ができるアプリを作ってみる。
  5.  
  6. ## 1. 設置
  7.  
  8. ```sh
  9. npm init
  10. ```
  11.  
  12. まず、Moduleを入れる前に`package.json`からつくる。
  13. 特に設定はいらないので、全部基本設定で十分。
  14.  
  15. ```sh
  16. npm i -S jquery jimp tinycolor2
  17. ```
  18.  
  19. `jimp`と`tinycolor2`は純粋にJavascriptだけで作られたImage編集moduleである。
  20.  
  21. 次にアプリの起動部も入れておく
  22.  
  23. ```js
  24. const electron = require('electron')
  25. const app = electron.app
  26. const BrowserWindow = electron.BrowserWindow
  27.  
  28. var mainWindow = null
  29.  
  30. app.on('window-all-closed', function () {
  31. app.quit()
  32. })
  33.  
  34. app.on('ready', function () {
  35. mainWindow = new BrowserWindow({width: 800, height: 600})
  36.  
  37. mainWindow.loadURL('file://' + __dirname + '/main-browser/index.html')
  38.  
  39. mainWindow.on('closed', function () {
  40. mainWindow = null
  41. })
  42. })
  43. ```
  44.  
  45. ## 2. Component 配置
  46.  
  47. `index.html`
  48.  
  49. ```html
  50. <!DOCTYPE html>
  51. <html>
  52. <head>
  53. <title>Picmod</title>
  54. <style>
  55. body {
  56. margin: 0;
  57. }
  58. #canvas {
  59. width: 100%;
  60. }
  61. </style>
  62. </head>
  63. <body>
  64. <div id='mainWindow'>
  65. <div id='pallet'>
  66. <button id='open'>Open</button>
  67. <button id='reset'>Reset</button>
  68. <button id='filter1'>Filter1</button>
  69. <button id='filter2'>Filter2</button>
  70. <button id='filter3'>Filter3</button>
  71. </div>
  72. <img id='canvas'>
  73. </div>
  74. <script>
  75. require('./index.js')
  76. </script>
  77. </body>
  78. </html>
  79. ```
  80.  
  81. 簡単に写真を編集するボタンとイメージたぐを配置した。
  82.  
  83. ## 3. 機能実装
  84.  
  85. `index.js`を次のように作成する。
  86.  
  87. ```js
  88. const fs = require('fs')
  89. const path = require('path')
  90. const $ = require('jquery')
  91. const Jimp = require('jimp')
  92. const electron = require('electron')
  93. const remote = electron.remote
  94. const data = {
  95. originalPath: null,
  96. tempPath: null
  97. }
  98.  
  99. function getPicturePath () {
  100. return remote.app.getPath('pictures')
  101. }
  102.  
  103. function getTempPath (filename) {
  104. var dataPath = remote.app.getPath('appData')
  105. if (filename != null) return path.join(dataPath, filename)
  106. return dataPath
  107. }
  108.  
  109. function openImage () {
  110. var selectedPaths = remote.dialog.showOpenDialog({
  111. defaultPath: getPicturePath(),
  112. properties: ['openFile'],
  113. filters: [{
  114. name: 'Images',
  115. extensions: ['jpg', 'png', 'gif']
  116. }]
  117. })
  118. if (selectedPaths == null) return null
  119. return selectedPaths[0]
  120. }
  121.  
  122. function copyFile (originalPath, targetPath, cb) {
  123. var rstream = fs.createReadStream(originalPath)
  124. var wstream = fs.createWriteStream(targetPath)
  125. rstream.pipe(wstream)
  126. rstream.on('end', cb)
  127. }
  128.  
  129. $('#open').on('click', function () {
  130. if (data.tempPath != null) {
  131. fs.unlinkSync(data.tempPath)
  132. data.originalPath = null
  133. data.tempPath = null
  134. }
  135.  
  136. var originalPath = openImage()
  137. if (originalPath != null) {
  138. data.originalPath = originalPath
  139. var filename = path.basename(originalPath)
  140. data.tempPath = getTempPath(filename)
  141.  
  142. copyFile(data.originalPath, data.tempPath, function () {
  143. $('#canvas').attr('src', 'file://' + data.tempPath + '?' + new Date().getTime())
  144. })
  145. }
  146. })
  147.  
  148. $('#reset').on('click', function () {
  149. copyFile(data.originalPath, data.tempPath, function () {
  150. $('#canvas').attr('src', 'file://' + data.tempPath + '?' + new Date().getTime())
  151. })
  152. })
  153.  
  154. $('#filter1').on('click', function () {
  155. if (data.tempPath == null) return false
  156. Jimp.read(data.tempPath, function (err, image) {
  157. if (err) {
  158. console.error(err)
  159. }
  160. image.posterize(5).write(data.tempPath, function () {
  161. $('#canvas').attr('src', 'file://' + data.tempPath + '?' + new Date().getTime())
  162. })
  163. })
  164. })
  165.  
  166. $('#filter2').on('click', function () {
  167. if (data.tempPath == null) return false
  168. Jimp.read(data.tempPath, function (err, image) {
  169. if (err) {
  170. console.error(err)
  171. }
  172. image.sepia().write(data.tempPath, function () {
  173. $('#canvas').attr('src', 'file://' + data.tempPath + '?' + new Date().getTime())
  174. })
  175. })
  176. })
  177.  
  178. $('#filter3').on('click', function () {
  179. if (data.tempPath == null) return false
  180. Jimp.read(data.tempPath, function (err, image) {
  181. if (err) {
  182. console.error(err)
  183. }
  184. image
  185. .color([
  186. { apply: 'hue', params: [ -90 ] }
  187. ])
  188. .write(data.tempPath, function () {
  189. $('#canvas').attr('src', 'file://' + data.tempPath + '?' + new Date().getTime())
  190. })
  191. })
  192. })
  193. ```
  194.  
  195. 全体的な機能はOpen buttonから写真を開いて、写真に幾つかの効果をいれてみることである。Jqueryは普通につかえるし、Jimpの使い方自体はあまり気にしなくていい。
  196.  
  197. 今回重要であるのは、わざわざ`remote`から`dialog`をよびだしていることである。
  198. 一般的にWeb appでファイルを受け取るときは`<input type='file'>`を使う。
  199. しかし、今回の場合は`dialog`を呼び出したほうがいい。なぜなら、Electronも一応Chromeであって、`input`タグからファイルを選択しても正確な経路が取れない。
  200. (保安的な理由で`C:\fakepath\~`のように変な経路を返す)
  201. なので、正確な経路を取るためには`dialog`を使う必要がある。
  202.  
  203. では、実行してみよう。
  204.  
  205. ## 4. Web worker?
  206.  
  207. 実行してみると一応働くと思うが、3番目のフィルターがかなり重いと思う。さらい、致命的な問題はJimpが純粋なJSでかかれているため、画面(Renderer process)が完全に凍ってしまう。
  208.  
  209. 今回はその問題を解決するために、他のProcessから重い作業を起動させて、Renderer processが止まらないようにする。
  210. ここでWeb workerを使えばいいと思うが、ElectronはWeb workerが使えない。
  211. 正直に言うといらない。殆どのNodeのmoduleがElectronでも使えるので、`child_process`を使えば簡単に解決できる。
  212.  
  213. では、やってみよう。
  214.  
  215. まず、アプリが凍っているのかを確認するため簡単にlogを吐き出すボタンを`index.html`に追加する。
  216.  
  217. ```html
  218. <button onclick='console.log("still alive!")'>Check</button>
  219. ```
  220.  
  221. では、`child_process#fork`でフィルター3の部分を実行できるようにコードを分離してみよう
  222.  
  223. `index.js`
  224.  
  225. ```js
  226. var worker = null
  227. const ChildProcess = require('child_process')
  228. $('#filter3').on('click', function () {
  229. if (data.tempPath == null) return false
  230. if (worker) return false
  231. console.log(data.tempPath)
  232.  
  233. worker = ChildProcess.fork(path.join(__dirname, 'filter3.js'), [data.tempPath])
  234. worker.on('exit', function () {
  235. console.log('done')
  236. $('#canvas').attr('src', 'file://' + data.tempPath + '?' + new Date().getTime())
  237. worker = null
  238. })
  239. })
  240.  
  241. ```
  242.  
  243. `filter3.js`
  244.  
  245. ```js
  246. const Jimp = require('jimp')
  247.  
  248. Jimp.read(process.argv[2], function (err, image) {
  249. if (err) {
  250. console.error(err)
  251. }
  252. image
  253. .color([
  254. { apply: 'hue', params: [ -90 ] }
  255. ])
  256. .write(process.argv[2], function () {
  257. process.exit()
  258. })
  259. })
  260. ```
  261.  
  262. 実行してみると、フィルター3が処理中であっても、Checkボタンが働くと思う。
  263.  
  264. ## 5. Native module
  265.  
  266. しかし、これは一番最適なゴールではない。
  267. 本質的に遅すぎることが問題なので、Native moduleを入れる必要がある。
  268.  
  269. ```sh
  270. npm i -S lwip
  271. ```
  272.  
  273. LwipもJimpもほぼおなじものであるが、Native moduleである。
  274.  
  275. `index.js`の3番めのFilterを次のように書き直す。
  276.  
  277. ```js
  278. $('#filter3').on('click', function () {
  279. if (data.tempPath == null) return false
  280.  
  281. lwip.open(data.tempPath, function (err, image) {
  282. if (err) {
  283. throw err
  284. }
  285. image
  286. .hue(90, function (err, image) {
  287. image.writeFile(data.tempPath, function (err) {
  288. if (err) throw err
  289. $('#canvas').attr('src', 'file://' + data.tempPath + '?' + new Date().getTime())
  290. })
  291. })
  292. })
  293. })
  294. ```
  295.  
  296. 一応コードはこれで終わる。
  297. しかし、実行をしてみるとできないと思う。
  298. 理由はNative moduleは今使っているNodeに合わせてRebuildしなきゃならない。
  299. ElectronもNodeが実装されているが、今設置したNPMはそのNodeから実行されていない。
  300. つまり、バイナリーが設置されたらRebuildする必要がある。
  301. その役割は、`electron-rebuild`というものがする。
  302.  
  303. ```sh
  304. npm i -D electron-prebuilt electron-rebuild
  305. ```
  306.  
  307. `electron-rebuild`は`electron-prebuilt`を必要とするので、改めて設置する。
  308.  
  309. 次にRebuildさせる。
  310.  
  311. ```sh
  312. ./node_modules/.bin/electron-rebuild
  313. ```
  314.  
  315. メッセージは何も出ないけど、これができたらおわりである。
  316. 実行してみるとめっちゃ時間がかかった3番めのFilterがかなり早くなっていることがわかると思う。
  317.  
  318. ### Tip
  319.  
  320.  
  321. ちなみに、`package.json`の`scripts.postinstall`に`electron-rebuild`入れたら、次から設置するNative moduleは勝手にRebuildされるようにしたらかなり楽になる。
  322.  
  323. ```js
  324. {
  325. "scripts": {
  326.   "start": "electron index.js",
  327.   "postinstall": "electron-rebuild"
  328. },
  329. }
  330. ```
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement