react-native-canvas icon indicating copy to clipboard operation
react-native-canvas copied to clipboard

Problem with resizing canvas

Open edbrito opened this issue 6 years ago • 10 comments

I've been having the same problem. This is my code:

import React, { Component } from 'react';
import {
  AppRegistry,
  View,
  PanResponder,
} from 'react-native';

import Canvas, {Image as CanvasImage} from 'react-native-canvas';

export default class ImagePainter extends Component {
  constructor(props) {
    super(props);
  }

  state = {
      image: this.props.image,
      originalImage: this.props.image,
      width: this.props.width,
      height: this.props.height,
      positionX: undefined,
      positionY: undefined,
      canvas: undefined,
  };

  panResponder = PanResponder.create({
    onStartShouldSetPanResponder: (evt, gestureState) => true,
    onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
    onMoveShouldSetPanResponder: (evt, gestureState) => true,
    onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,

    onPanResponderGrant: (evt, gestureState) => {
      const x = Math.floor(evt.nativeEvent.locationX);
      const y = Math.floor(evt.nativeEvent.locationY);
      this.positionX = x;
      this.positionY = y;
    },
    onPanResponderMove: (evt, gestureState) => {
      if(!this.canvas){
        return;
      }

      const x0 = this.positionX;
      const y0 = this.positionY;
      const x1 = Math.floor(evt.nativeEvent.locationX);
      const y1 = Math.floor(evt.nativeEvent.locationY);
      const ctx = this.canvas.getContext('2d');
      ctx.moveTo(x0, y0);
      ctx.lineTo(x1, y1);
      ctx.stroke();
      this.positionX=x1
      this.positionY=y1;
    },
    onPanResponderTerminationRequest: (evt, gestureState) => true,
    onPanResponderRelease: (evt, gestureState) => {
    },
    onPanResponderTerminate: (evt, gestureState) => {
    },
    onShouldBlockNativeResponder: (evt, gestureState) => {
      return true;
    },
  });

  componentDidMount() {
    const canvas = this.canvas;
    if(canvas){
      canvas.width = this.props.width;
      canvas.height = this.props.height;
      this.renderCanvas();
    }
  }

  renderCanvas = async () => {
    if (this.canvas) {
      const canvas = this.canvas;
      const ctx = await canvas.getContext('2d');
      const image = new CanvasImage(canvas, this.props.height, this.props.width);
      image.addEventListener('load', function() {
        ctx.drawImage(image, 0, 0);
      });
      image.addEventListener('error', err => console.log(err))
      image.src = `data:image/jpeg;base64,${this.state.image}`;
    } else {
      console.log('No canvas?')
    }
  }

  componentDidUpdate() {
    this.renderCanvas();
  }

  render() {
    return (
          <View {...this.panResponder.panHandlers} style={{width: this.props.width, height: this.props.height}}>
            <Canvas ref={ canvas => this.canvas = canvas } 
                    style={{borderWidth: 3, borderStyle: 'dashed', width: this.props.width, height: this.props.height}}
                    width={this.props.width}
                    height={this.props.height}
                  />
          </View>
    );
  }
}

AppRegistry.registerComponent('ImagePainter', () => ImagePainter);

From what is written here, it should work.

I narrowed down the problem to the resizing. It looks like resizing changes the reference to the canvas?

On my componentDidMount if I leave the canvas.width and height assignments, nothing shows up. If I comment it how, I get a canvas that's too small (300x150px instead of 300x400) but everything works.

I installed react-native-canvas 2 days ago so it should be the most up-to-date...

Originally posted by @edbrito in https://github.com/iddan/react-native-canvas/issues/94#issuecomment-504378489

edbrito avatar Jun 21 '19 11:06 edbrito

Posted as a new issue since the previous thread was already closed when I posted the comment... Sorry.

edbrito avatar Jun 21 '19 11:06 edbrito

Do you pass width and height to your component?

iddan avatar Jun 23 '19 07:06 iddan

Yes. I've confirmed it. The width and height are being passed.

I've also added code to restore the original image and it restores but as soon as I press to draw, it goes back to the altered canvas that I had before I restored the image to the original one.

edbrito avatar Jun 23 '19 07:06 edbrito

I removed the need to draw the image on the react native canvas by drawing it with a regular Image component and overlaying the react-native-canvas component on top of the image by using absolute positioning.

However, I can't resize the component without facing the same problems. As in, it doesn't draw anything on the canvas after having been resized.

edbrito avatar Jun 26 '19 10:06 edbrito

i'm having the same issue, any solutions?

Azus5 avatar Mar 31 '20 11:03 Azus5

I'm facing the same problem as well. @iddan do you perhaps have an example where a Canvas has dimensions X, resizes to dimensions Y, draws something and it shows up? Just to rule out us doing something wrong.

Edit: I looked into it more and it appears that this is expected behaviour. When the canvas its width or height changes, it will clear everything that was rendered. I've verified this in non-native React and sources corroborate this: https://stackoverflow.com/a/5517885/1864167 & https://stackoverflow.com/questions/56120082/how-to-get-correct-width-and-height-for-a-canvas-in-react#comment98873492_56120235

Note that this might just be me and the others their problem is slightly different. I'll update this if I find myself in the same situation as they are. That being said, this example draws a rectangle on the canvas, resizes the canvas and draws a new rectangle: https://gist.github.com/Vannevelj/eca9ab7da0d543c84964ecdbcad00879

Vannevelj avatar May 08 '20 11:05 Vannevelj

Found a possible fix since it worked for me: inside the handleCanvas function (as in the documentation), i set canvas.width to equal Dimensions.get('window').width (as in the react native documentation for getting device height and width). That was the only way i managed to set a height and a width to the canvas element that is not 150X300. I'm using a bare react native project, with very minimal dependencies which include react, react native, react native canvas, react native webview, react redux, redux, react router native and styled components. My react native version is 0.63.3 according to my package.json and react native canvas is 0.1.37.

dandan-drori avatar Oct 09 '20 14:10 dandan-drori

(sorry if this is hijacking this issue but I think this is related)

The problem with the technique @dandan-drori mentioned is that the transformation matrix is no longer the identity matrix.

I confirmed this by manually exposing the getTransform function for CanvasRenderingContext2D in my node_modules.

isIdentity is true if I leave my Canvas component untouched, or add a height+width style or if I add the height and width props. However, if I modify the canvas object itself in the handler to make the canvas bigger it changes the transformation matrix (in my case where a and d are 2 instead of 1)

It seems this is related to my current issue where scaling/translating isn't working as expected.

I think ideally the Canvas would accept a height and width prop that would pass down and also apply both the WebView and the RNCWebView so they would size correctly? (as even

Example function passed to <Canvas ref={handleCanvas} />

function handleCanvas(canvas) {
    canvas.width = myCalculatedWidth;
    canvas.height = myCalculatedHeight;
}

I'm testing this on an iPad using Expo Go with React Native and Expo for reference.

EDIT: Oh, I've just noticed autoScaleCanvas so it seems the default matrix being non-identity is intended. I'll have to see if I can work around this but it seems to still be causing issues.

James-Firth avatar Feb 23 '21 05:02 James-Firth

Similar to @dandan-drori , I used the onLayout handler to get the size of my canvas container (which varies a bit based on the device). From that handler I set the width and height of the ref.

Also, TypeScript was showing an error when I tried to pass in the width and height as props for some reason, so I skipped those.

const onContainerLayout = (event: LayoutChangeEvent) => {
  const { layout } = event.nativeEvent
  if (canvasRef?.current) {
    canvasRef.current.height = layout.height * 0.6
    canvasRef.current.width = layout.width
  }
}
<View onLayout={onContainerLayout}>
  <Canvas ref={canvasRef} />
</View>

blwinters avatar Oct 15 '22 18:10 blwinters

@James-Firth Hi, I am experiencing similar issues with scaling, did you manage to find a work around?

Pingou avatar Apr 30 '24 16:04 Pingou