Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem with resizing canvas #134

Open
edbrito opened this issue Jun 21, 2019 · 10 comments
Open

Problem with resizing canvas #134

edbrito opened this issue Jun 21, 2019 · 10 comments

Comments

@edbrito
Copy link

edbrito commented Jun 21, 2019

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 #94 (comment)

@edbrito
Copy link
Author

edbrito commented Jun 21, 2019

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

@iddan
Copy link
Owner

iddan commented Jun 23, 2019

Do you pass width and height to your component?

@edbrito
Copy link
Author

edbrito commented Jun 23, 2019 via email

@edbrito
Copy link
Author

edbrito commented Jun 26, 2019

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.

@Azus5
Copy link

Azus5 commented Mar 31, 2020

i'm having the same issue, any solutions?

@Vannevelj
Copy link

Vannevelj commented May 8, 2020

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

@dandan-drori
Copy link

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.

@James-Firth
Copy link

James-Firth commented Feb 23, 2021

(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.

@blwinters
Copy link

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>

@Pingou
Copy link

Pingou commented Apr 30, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants