LoginSignup
22
15

More than 5 years have passed since last update.

Electronでローカル画像ファイルを表示する

Last updated at Posted at 2018-10-11

概要

Electronのアプリケーションを開発中に、埋め込み画像を表示しようとして「Not allowed to load local resource」というエラーに遭遇したので、その対策をメモしておきます。

パッケージ化すると対策を打たなくても画像は表示されるかもしれません。
検証したら後日追記すると思います。

結論から言うと、fileプロトコルをやめて、<img src="data:image/png;base64,<画像をbase64化した文字列>"/> を使って解決しました。

相対パスでの指定の問題(別の問題が発生するため、後述の対策を実施)

Electronアプリは、Reactを使っていてcreate-react-appで作ったアプリで、それをElectron化したものです。

index.htmlの横に、logo.pngを配置、タグで、<img src="../build/logo.png"/>として表示させようとしていました。

画像は、Webアプリとしては表示されますが、Electron上ではsrc="file:///build/logo.png"となり、絶対パスで探すようになります。

この対策として、以下のサイトを参考に、protocol.interceptFileProtocol というAPIで、"file:///%RELATIVE_PATH%" (RELATIVE_PATHはマジックワード)で始まっていれば、相対パスとしてハンドルするよう実装しました。

imgタグの指定は、<img src="%RELATIVE_PATH%../build/logo.png" />とし、ソースはこんな感じです。

protocol.interceptFileProtocol('file', (req, callback) => {
    const requestedUrl = req.url.substr(7);

    if (path.startsWith("/%RELATIVE_PATh%") {
      callback(path.normalize(path.join(__dirname, requestedUrl.substring("/%RELATIVE_PATh%".length)));
    } else {
      callback(requestedUrl);
    }

この対策は、Macでは動作しましたが、Windowsでは「Not allowed to load local resource」というエラーに遭遇し、動作しませんでした。

Not allowed to load local resource 対策(解決済み)

Googleでいくつか調べましたが、WebsecurityをOffにする、create-react-appをeject(不可逆の操作で、create-react-appの良さが失われる)したり、webpackのコンフィグを変えるなどといったもので、適用しても効果がありませんでした。

結局は、以下に引用した https://stackoverflow.com/questions/50272451/electron-js-images-from-local-file-system に書かれているように、fileプロトコルを使っている限りセキュリティをOffにするなどの解決策しかなく、問題は解決しませんでした。

Electron by default allows local resources to be accessed by render processes only when their html files are loaded from local sources with the file:// protocol for security reasons.

If you are loading the html from any http:// or https:// protocol even from a local server like webpack-dev-server, access to local resources is disabled.

If you loading html pages from a local server only during development and switching to local html files in production, you can disable websecurity during development, taking care to enable it in production.

最終的な解決策

`<img src="data:image/png;base64,<画像をbase64化した文字列>"/> というやり方で解決しました。

解決した後のフォルダ構成は、以下の通りです。

  build/ <= publicとsrcのビルド結果が格納される。
  public/index.html
         logo.png
  src/ ...
       libs/ImageLoader.js
       main.js <= ElectronのMainプロセスの起動ファイル

解決のためのステップは以下の通りです。
1. main.jsで、imagesフォルダ以下のpng/jpgファイルをbase64化し、globalの変数に詰める。
2. ImageLoader.jsクラスを作成し、srcに渡す文字列を生成する。
3. jsxで、imgタグに、step2で生成した文字列を渡す。

ソースは以下の通り。

  • 画像ファイルを捜査、globalマップに詰めるところまで。
main.js
const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')
const url = require('url')
const fs = require('fs')
const info = {
  win: null,
  images: {}
}

global.info = info

let win
function createWindow () {
  win = new BrowserWindow({width: 800, height: 600, resizable:true})
  info.win = win

  // images以下のフォルダを捜査
  const files = fs.readdirSync(path.join(__dirname, '/../build/images'))
  files.forEach((file)=>{
    const ext = path.extname(file)
    if(ext === '.png' || ext === '.jpg') {
      const binary = fs.readFileSync(path.join(__dirname, '/../build/images/' + file))
      // base64化
      const base64data = new Buffer(binary).toString('base64')
      // マップに詰める
      info.images[file] = {
        type: ext.substring(1),
        name: file,
        data: base64data
      }
    }
  })
  // 通常のElectronのメイン処理
  win.loadURL(url.format({
    pathname: path.join(__dirname, '/../build/index.html'),
    protocol: 'file:',
    slashes: true
  }))
  // 以下省略・・・
  • ImageLoader.js(グローバルマップから、data:image/png,base64,... を生成)
ImageLoader.js
const electron = window.require('electron')
//globalを取得するには、electron.remoteで取得する
const remote = electron.remote

class ImageLoader {
  static loadSrc = (imagePath) => {
    const info = remote.getGlobal('info')
    const image = info.images[imagePath]
    for(let key in info.images) {
      console.log(key + " " + info.images[key].type + " " + info.images[key].name)
    }
    if(image === undefined)
      return "image not found"
    // data:image/<png|jpg>;base64,<base64 encoded data>の生成
    return `data:image/${image.type};base64,${image.data}`
  }
}

export default ImageLoader
  • 画像表示させるところ
App.jsx
import React from 'react'
import ImageLoader from '../libs/ImageLoader'
/* アプリケーション画面の定義 */
const App = () => (
  <div>
    <img src={ImageLoader.loadSrc('logo.png')} height="64px" width="64px"/>
  // 以下省略・・・

さいごに

対策の備忘録として記載しています。
他の方法もあるかもしれませんので、ご存知でしたらお知らせいただければ幸いです。

22
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
15