ぽっちぽちにしてやんよ

技術ネタとかアプリに関する話とか

React Nativeで画像をdata-uriにしてハンドリングする

こんにちは,ぽちです.

来週7/12のReact Native Meetup#6でLTします.

ネタはたぶんExpo関連の何かをゆるっと話します.

react-native-meetup.connpass.com

本題です.

React Nativeでインターネット上の(Assetじゃない)画像ファイルをTwitterなどにシェアをしたい時ことが有ったのですが,その時にちょっとハマった知見を共有します.

困ったこと

WebサービスのAPIを叩くと,画像のリソースは基本的にURLで返されます.

React Native上表示させるのであれば,返されたURLをImageコンポーネントのsourceアトリビュートに{uri: xxxx}という形で突っ込めばいいのですが,それを何処かに投稿するとなるとURLのままだと困るケースがあります.

Node.jsだと単純に画像のURLにGETしてpipeでStreamを繋いで投稿先にPOSTするとかが使えるかもしれませんが,React Nativeだと同等の手段がなく,困りました.

ShareコンポーネントはURLが受けれる

今回必要になったのは,Twitterへのシェアだったので,

Share コンポーネントを使えばURLのままでも行けるのでは?と思ったのですが,クライアントによってはいい感じで表示してくれなかったため,data-uriの形にして画像としてShareコンポネントに突っ込みたいからでした.

解決法その1: Expo.takeSnapshotAsync

まず初めに思いついたのは,Expoのモジュールにある画面のスナップショットを撮るメソッドtakeSnapshopAsyncを使って,Imageコンポーネントで描画し,そのImageコンポーネントのスナップショットを撮るという手法でした.

<Image
  source={{ uri: this.state.thumbnail }}
  resizeMode="contain"
  style={styles.thumbnail}
  ref="thumbnail"
/>

という形で,得られた画像のurlをthis.state.thumbnailなどに入れておき,それをImageコンポーネントで描画します.

takeSnapshopAsyncで指定するために,refアトリビュートを設定しておきます.

share() {
  Expo.takeSnapShotAsync(this.refs.thumbnail, {
    format: 'jpg',
    quality: 0.9,
    result: 'data-uri',
  })
  .then((image)=>{
    Share.share({
      message: 'message',
      url: img,
      title: 'Share'
    }, {
      dialogTitle: 'Share',
      excludedActivityTypes: [],
      tintColor: 'green'
     });
  });
}

このようにtakeSnapShotAsyncresultプロパティにdata-uriを指定して画像を取得してShareコンポーネントのurlプロパティに設定すればTwitterなどのアプリに画像をシェアすることが出来ます.

しかし,実際にシェアされた画像を見ると・・・

f:id:poChi:20170705135745p:plain

このように白帯が入ってしまっています.

これはImageコンポーネントのスタイルにbackgroundColor: 'transparent'を指定しても同様です.

自分はこれがどうしても気に食わなかったので別の方法を探しました.

解決法その2: buffer

次は,bufferというnpmモジュールを使いました.

GitHub - feross/buffer: The buffer module from node.js, for the browser.

これは,node.jsの標準で用意されているBufferをブラウザにも持ってくるという意図で作られているモジュールです.

これを使って,画像のarraybufferを取得しdata-uriに変換して渡してやろうと考えました.

import axios from 'axios';
import { Buffer } from 'buffer/';
axios
  .get(this.state.thumbnail, {
    responseType: 'arraybuffer'
  })
  .then(response => {
    const image = Buffer.from(response.data).toString('base64');
    Share.share({
      message: `message`,
      url: `data:${response.headers['content-type'].toLowerCase()};base64,${image}`,
      title: 'Share'
    }, {
      dialogTitle: 'Share',
      excludedActivityTypes: [],
      tintColor: 'green'
    });
});

buffer/となっているのはTypoではなく,そう指定するようにドキュメントに書かれています.

画像のarraybufferの取得は GitHub - mzabriskie/axios: Promise based HTTP client for the browser and node.js を使って,responseTypearaybufferを指定してGETすることで取得しました.

request.jsとかsuperagentとかfetchでも出来るような気もします.

得られたarraybufferをBuffer.fromに入れ,toString('base64')で変換します.

このままだと,data-uriではないので,data:${response.headers['content-type'].toLowerCase()};base64,${image}という形にして,data-uriとしてShareコンポーネントのurlプロパティに入れます.

これで元画像を白い帯などが付与されることなく共有できます.