iddan / react-native-canvas

A Canvas component for React Native
MIT License
981 stars 172 forks source link

How to render an image from React Native Image Picker #78

Closed hzburki closed 5 years ago

hzburki commented 5 years ago

Hello

I'm making an app which takes a picture from the phone's camera, overlays the timestamp as text and a logo as watermark on the picture. And let's the user store it on their phone.

I'm using react-native-image-picker to access the camera. I get the image and can display it using RN's Image or ImageBackground components, but can't seem to display it on the canvas.

Initial Canvas

_initCanvas = (canvas) => {
  let ctx = canvas.getContext('2d');
  canvas.width = 800;
  canvas.height = 1200;

  let image = new CanvasImage(canvas, 200, 200);
  image.src = this.state.image.path; 

  image.addEventListener('load', () => {
    ctx.fillStyle = 'purple';
    ctx.fillRect(0, 0, 100, 100);
    ctx.drawImage(image, 10, 10, 200, 200);
  });
}

Image Picker Response

image: {
  data: "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQ",
  fileName: "image-14a74c48-fcf3-4528-bdf1-b7c925acd293.jpg",
  fileSize: 133818,
  height: 600,
  isVertical: true,
  originalRotation: 0,
  path: "/storage/emulated/0/Android/data/com.image_watermark/files/Pictures/image-14a74c48-fcf3- 
  4528-bdf1-b7c925acd293.jpg",
  type: "image/jpeg",
  uri: "file:///storage/emulated/0/Android/data/com.image_watermark/files/Pictures/image-14a74c48- 
  fcf3-4528-bdf1-b7c925acd293.jpg", 
  width: 338,
}

Note The canvas is rendered after the response from react-native-image-picker is added to the state. I have tried 'uri', 'path' and 'data' attributes but always get a blank canvas. I do get the purple rectangle though.

End Result I want to be able to add the picture on canvas. Then add a logo and text over the picture and save it to my mobiles camera roll.

hzburki commented 5 years ago

Any progress ?? I'm kinda on a deadline ?? 😨

Riant commented 5 years ago

I want to play the local video with html element <video src="file:///....."></video> in WebView, then use canvas to draw the frame data as picture, but the local video from React Native Image Picker can not play.

Maybe because the browser security rules, we can not load local media file in WebView, but I am not so sure.

iddan commented 5 years ago

@hzburki there is no progress to be made, it's a use case needed to be figured out not a bug.

Riant commented 5 years ago

@hzburki

Maybe because the browser security rules, we can not load local media file in WebView, but I am not so sure.

I am sorry I was wrong. I found I can show local image with <img src='file:///...' /> in WebView html.

Maybe we can try to add support for this use case. @iddan

iddan commented 5 years ago

What's needed to be done for it to be supported?

hzburki commented 5 years ago

@Riant you can display the local file using the uri key in the response from react-native-image-picker in canvas ??? Can you share the code please ???

Btw I can show the image from image-picker in Image and ImageBackground components easily. Doesn't take long to load either.

the-unknown commented 5 years ago

Hey, so I just would like to know what this is about: I was planning to convert a web-based application that will let you turn your uploaded photos into memes (put basic shapes and text on it) into a RN app. But if I get this issue right, you are not able to place an image from your local phone gallery on the canvas?

iddan commented 5 years ago

I think you can. Many have reported they were able to pull it off. I think the actual issue is retrieving the image back (I'm accepting PRs). BTW this use case is why I made react-native-canvas initially.

luisfuertes commented 5 years ago

Thanks for component!

Finally could you load image from image picker and show it? Can you add example please

Riant commented 5 years ago

I did that with my own webview component. I want to get the video frame-image with canvas, but I found it is not support on some old Android phone, so we did that with native code on Android, and webview-canvas on iOS.

Some main code below, wish it's useful for you guys:

html.js

const html = `
  <!DOCTYPE html>
  <html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <title>video-canvas</title>
    <style>
        html, body { margin: 0; padding: 0;}
        body { position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden;}
    </style>
  </head>
  <body>
    <video id='video' src='#' muted playsinline></video>
    <canvas id='canvas' width='100' height='100'></canvas>

    <script>
      var canvas = document.getElementById('canvas')
      var targets = {
        video: document.getElementById('video'),
            canvas: canvas,
        context2d: canvas.getContext('2d')
      }

      targets.video.addEventListener('loadeddata', function(){
        targets.canvas.width = targets.video.videoWidth
        targets.canvas.height = targets.video.videoHeight
        postMessage(JSON.stringify({
          target: 'video',
          type: 'loadeddata',
          width: targets.video.videoWidth,
          height: targets.video.videoHeight
        }))
      })

      document.addEventListener('message', receiveMessage, false)

      function receiveMessage(e) {
        var payload = JSON.parse(e.data)
        switch (payload.type) {
          case 'setVideoPath': {
            targets.video.src = payload.value
            targets.video.load()
            break
          }
                case 'set': {
                    Object.keys(payload.params).forEach(key => {
                        targets[payload.target][key] = payload.params[key]
                    })
            if (payload.target === 'video' && payload.params.src) targets.video.load()
                    break
                }
                case 'call': {
                    targets[payload.target][payload.method]()
            break
                }
          case 'getFrameAsImage': {
            targets.context2d.drawImage(targets.video, 0, 0, targets.canvas.width, targets.canvas.height)
            postMessage(JSON.stringify({
              target: 'canvas',
              type: 'gotDataUrl',
              width: targets.canvas.width,
              height: targets.canvas.height,
              localIdentifier: targets.video.localIdentifier,
              data: targets.canvas.toDataURL(payload.format || 'image/jpeg', 1)
            }))
            break
          }
        }
      }
    </script>
  </body>
  </html>
`

export default html

trimmer.ios.js

// 通过 Webview Canvas 截取视频帧图片

import React, { Component } from 'react'
import {
  Text,
  View,
  Image,
  WebView,
  Alert,
  ActivityIndicator,
} from 'react-native'
import RNFS from 'react-native-fs'

import html from './html'

class VideoTrimmer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      doing: this.props.video && this.props.video.path ? 1 : 0,
    }
  }

  onDone(path) {
    this.setState({doing: 0})
    this.props.onDone && this.props.onDone({path, from: 'video', second: this.props.second || 0})
  }

  onMessage(e) {
    const data = JSON.parse(e.nativeEvent.data)
    if (data.type === 'loadeddata') {
      if (global.TEMP[this.props.video.localIdentifier]) return this.onDone(global.TEMP[this.props.video.localIdentifier])
      this.postMessage({type: 'getFrameAsImage', format: 'image/jpeg'})
    } else if (data.type === 'gotDataUrl') {
      const frameImagePath = global.CachePATH + '/frame-0-' + new Date().getTime() + '.jpg'
      // Alert.alert('ds', data.data)
      RNFS.writeFile(frameImagePath, data.data.replace('data:image/jpeg;base64,', ''), 'base64').then(() => {
        // Alert.alert('ss', frameImagePath)
        if (data.localIdentifier) global.TEMP[data.localIdentifier] = frameImagePath
        this.onDone(frameImagePath)
      })
    }
  }

  postMessage(data) {
    this.webview.postMessage(JSON.stringify(data))
  }

  getPreviewImage() {
    if (global.TEMP[this.props.video.localIdentifier]) return this.onDone(global.TEMP[this.props.video.localIdentifier])
    if (this.state.doing === -1) return
    this.setState({doing: -1})
    this.postMessage({type: 'set', target: 'video', params: {src: this.props.video.path, localIdentifier: this.props.video.localIdentifier}})
  }

  onWebviewLoadend() {
    if (this.props.video && this.props.video.path) this.getPreviewImage()
  }

  componentWillReceiveProps(nextProps) {
    if (this.state.doing === 1) return
    if (nextProps.image.from === 'refresh' || nextProps.second !== this.props.second || (this.props.image.from === 'video' && nextProps.video.path !== this.props.video.path)) {
      this.setState({doing: 2})
    }
  }
  componentDidUpdate() {
    if (this.state.doing === 2 && this.props.video && this.props.video.path) this.getPreviewImage()
  }

  render() {
    return (
      <View style={[{width: this.props.width, height: this.props.height}, this.props.style || {}]}>
        <WebView
          mediaPlaybackRequiresUserAction={false}
          originWhitelist={['*']}
          // allowFileAccess={true}
          source={{baseUrl: '', html}}
          onMessage={e => this.onMessage(e)}
          allowsInlineMediaPlayback={true}
          ref={ele => this.webview = ele}
          onLoadEnd={() => this.onWebviewLoadend()}
          style={{width: 5, height: 5, position: 'absolute', top: 0, right: 0, opacity: 0}}
        />
        {this.props.image && this.props.image.path &&
          <Image source={{uri: this.props.image.path}} resizeMode='contain'
            style={{width: this.props.width, height: this.props.height}}
          />
        }
        {this.state.doing !== 0 &&
          <View style={[baseStyles.overlayer, baseStyles.flex0Center]}>
            <ActivityIndicator color='#FFF' />
            {this.props.doingText && <Text style={{marginTop: 20, color: '#999', fontSize: 12}}>{this.props.doingText}{this.state.doing}</Text>}
          </View>
        }
      </View>
    )
  }
}

export default VideoTrimmer

Then, use it like below:

// import VideoTrimmer from '../xxx/trimmer'

// render: 
<VideoTrimmer video={this.state.video} image={this.state.image} second={0}
                  onDone={image => this.setState({image})}
                  width={width - 40} height={mediaHeight}
                  doingText='Waiting please'
                  ref={ele => this.VideoTrimmer = ele}
                />
maximus123123 commented 5 years ago

I can only do it with base64 from RNFetchBlob

RNFetchBlob.fs.readFile(this.state.photo,'base64')
      .then((data) => {
        const image2 = new CanvasImage(canvas);
        image2.src ='data:image/png;base64,'+data;
        image2.addEventListener('load', () => {
                  context.drawImage(image2,0,0,width,height)
                  //be careful of the image ratio!
       })

});
AntonZelenkov1997 commented 3 years ago

Hi, I have this problem too. I can't visualize image in canvas with " file:///data/user/0/host.exp.exponent/cache/ExperienceData/..." uri. Any solutions?

P.S. I can visualise images with web paths and purple boxes, but not with local path.