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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃悰 Camera is not aligned properly when using useSkiaFrameProcessor #2762

Closed
5 tasks done
karventhan13 opened this issue Apr 20, 2024 · 44 comments 路 Fixed by #2931 or #2939
Closed
5 tasks done

馃悰 Camera is not aligned properly when using useSkiaFrameProcessor #2762

karventhan13 opened this issue Apr 20, 2024 · 44 comments 路 Fixed by #2931 or #2939
Labels
馃悰 bug Something isn't working

Comments

@karventhan13
Copy link

What's happening?

馃悰 [^4.0.0-beta.16] - Camera is not aligned properly when using useSkiaFrameProcessor but it's work with Screenshot_20240420_133231

Reproduceable Code

return (
    <SafeAreaView style={styles.container}>
      {!hasPermission && <Text>No Camera Permission.</Text>}
      {hasPermission && device != null && (
        <Camera
          style={StyleSheet.absoluteFill}
          device={device}
          isActive={true}
          photo={true}
          video={true}
          format={format}
          onError={(err)=> { console.log("err **",err)}}
          fps={fps}
          enableFpsGraph={true}
          frameProcessor={frameProcessor}  
          videoHdr={format.supportsVideoHdr}
          photoHdr={format.supportsPhotoHdr}
          enableZoomGesture={false}
        />
      )}
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

Relevant log output

none

Camera Device

none

Device

vivo y21

VisionCamera Version

[^4.0.0-beta.16]

Can you reproduce this issue in the VisionCamera Example app?

I didn't try (鈿狅笍 your issue might get ignored & closed if you don't try this)

Additional information

@karventhan13 karventhan13 added the 馃悰 bug Something isn't working label Apr 20, 2024
@shaozhenged

This comment was marked as spam.

@mrousavy
Copy link
Owner

I will never understand why people ignore the fact that I need log output. I will write a bot in the future to auto-close issues that don't have valid log output.

@mrousavy
Copy link
Owner

Maybe play around with useSkiaFrameProcessor hook, specifically the render() function in there. If you can figure out what's wrong, please submit a PR.
I'm on vacation this week

@mrousavy mrousavy changed the title 馃悰 [^4.0.0-beta.16] - Camera is not aligned properly when using useSkiaFrameProcessor 馃悰 Camera is not aligned properly when using useSkiaFrameProcessor Apr 22, 2024
@Space6188
Copy link

This happened when you using resizeMode={'contain'}

Try to delete this from Camera props

@mrousavy
Copy link
Owner

@Space6188 does it properly display without the resizeMode?

@mrousavy
Copy link
Owner

I can also reproduce this when using resizeMode="cover"...

@mrousavy
Copy link
Owner

Try playing around with the code from useSkiaFrameProcessor:

'worklet'
// rotate canvas to properly account for Frame orientation
canvas.save()
const rotation = getRotationDegrees(frame.orientation)
canvas.rotate(rotation, frame.width / 2, frame.height / 2)
// render the Camera Frame to the Canvas
if (paint != null) canvas.drawImage(image, 0, 0, paint)
else canvas.drawImage(image, 0, 0)
// restore transforms/rotations again
canvas.restore()

Maybe we're doing something wrong.

Also, the ImageProxy (which is what the native Frame type holds) contains a getSensorToBufferTransformMatrix() method - maybe we can use this Matrix to transform the Frame safely to view dimensions? cc @rodgomesc if you wanna look into this.

@rodgomesc
Copy link
Contributor

I will never understand why people ignore the fact that I need log output. I will write a bot in the future to auto-close issues that don't have valid log output.

then you will have zero issues in vc 馃槄

@rodgomesc
Copy link
Contributor

i tried all resizeMode possibilities, also all fit possibilities, the problem is not there

@mrousavy
Copy link
Owner

then it's probably the sensor to buffer transform matrix.

@Space6188
Copy link

@Space6188锌褉邪胁懈谢褜薪芯 谢懈 芯薪 芯褌芯斜褉邪卸邪械褌褋褟 斜械蟹 resizeMode?

No,my bad,sorry, it is not working too

@rodgomesc
Copy link
Contributor

i think that i found a fix for this, should send a pr some time tomorrow

@AndyRC6
Copy link

AndyRC6 commented Apr 29, 2024

In case it helps - I am able to reproduce this by passing in the format prop with a custom videoResolution to the camera. Removing the format prop seems to fix the issue.

Edit: Actually, removing format only fixes the white bar that appears to the left of the preview, and moves it down to the bottom. Which is less noticeable as it looks like that's just where the camera preview ends. But in my case, the preview should take up the entire screen, but doesn't when using the Skia frame processor.

@rodgomesc
Copy link
Contributor

rodgomesc commented Apr 30, 2024

it seems that canvas.drawImage draws the image with a default clip and matrix that are messed by the canvas rotation somehow? that鈥檚 confusing since if we use canvas.drawImageRect with the same src/dst props it draws correctly! in my tests it seem to solve the issue

diff --git a/package/src/skia/useSkiaFrameProcessor.ts b/package/src/skia/useSkiaFrameProcessor.ts
index 3feb336..fd69ca1 100644
--- a/package/src/skia/useSkiaFrameProcessor.ts
+++ b/package/src/skia/useSkiaFrameProcessor.ts
@@ -6,8 +6,34 @@ import { VisionCameraProxy, wrapFrameProcessorWithRefCounting } from '../FramePr
 import type { DrawableFrameProcessor } from '../types/CameraProps'
 import type { ISharedValue, IWorkletNativeApi } from 'react-native-worklets-core'
 import { WorkletsProxy } from '../dependencies/WorkletsProxy'
-import type { SkCanvas, SkPaint, SkImage, SkSurface } from '@shopify/react-native-skia'
+import { type SkCanvas, type SkPaint, type SkImage, type SkSurface, ColorType, AlphaType, Skia } from '@shopify/react-native-skia'
 import { SkiaProxy } from '../dependencies/SkiaProxy'
+import { InteractionManager } from 'react-native'
+
+// Mocked Frame Data for testings
+const mockedImageWidth = 1920
+const mockedImageHeight = 1080
+
+const pixels = new Uint8Array(mockedImageWidth * mockedImageHeight * 4)
+pixels.fill(255)
+let i = 0
+for (let x = 0; x < mockedImageWidth; x++) {
+  for (let y = 0; y < mockedImageHeight; y++) {
+    pixels[i++] = (x * y) % 255
+  }
+}
+
+const data = Skia.Data.fromBytes(pixels)
+const image = Skia.Image.MakeImage(
+  {
+    width: mockedImageWidth,
+    height: mockedImageHeight,
+    alphaType: AlphaType.Opaque,
+    colorType: ColorType.RGBA_8888,
+  },
+  data,
+  mockedImageWidth * 4,
+)!
 
 /**
  * Represents a Camera Frame that can be directly drawn to using Skia.
@@ -155,7 +181,7 @@ export function createSkiaFrameProcessor(
 
     // Convert Frame to SkImage/Texture
     const nativeBuffer = (frame as FrameInternal).getNativeBuffer()
-    const image = Skia.Image.MakeImageFromNativeBuffer(nativeBuffer.pointer)
+    // const image = Skia.Image.MakeImageFromNativeBuffer(nativeBuffer.pointer)
 
     return new Proxy(frame as DrawableFrame, {
       get: (_, property: keyof DrawableFrame) => {
@@ -165,11 +191,36 @@ export function createSkiaFrameProcessor(
             'worklet'
             // rotate canvas to properly account for Frame orientation
             canvas.save()
-            const rotation = getRotationDegrees(frame.orientation)
-            canvas.rotate(rotation, frame.width / 2, frame.height / 2)
-            // render the Camera Frame to the Canvas
-            if (paint != null) canvas.drawImage(image, 0, 0, paint)
-            else canvas.drawImage(image, 0, 0)
+
+            const rotationAngle = 0 //getRotationDegrees('portrait')
+            const { width: frameWidth, height: frameHeight } = getPortraitSize(frame)
+
+            // center of the canvas
+            const centerX = frameWidth / 2
+            const centerY = frameHeight / 2
+
+            const currentPaint = paint ?? Skia.Paint()
+
+            // only draw the rect by 40% of the frame size so we can vizualize the whole thing to debug :)
+            const reductionFactor = 0.4
+            const rectWidth = frameWidth * reductionFactor
+            const rectHeight = frameHeight * reductionFactor
+
+            // apply rotation around the center of the canvas
+            canvas.rotate(rotationAngle, centerX, centerY)
+
+            const rectX = centerX - rectWidth / 2
+            const rectY = centerY - rectHeight / 2
+
+            const srcRect = Skia.XYWHRect(0, 0, image.width(), image.height())
+            // Define the destination rectangle on the canvas
+            const dstRect = Skia.XYWHRect(rectX, rectY, rectWidth, rectHeight)
+            // Draw the image on the canvas
+
+            canvas.drawImageRect(image, srcRect, dstRect, currentPaint)
 
             // restore transforms/rotations again
             canvas.restore()
@@ -185,7 +236,7 @@ export function createSkiaFrameProcessor(
           return () => {
             'worklet'
             // dispose the Frame and the SkImage/Texture
-            image.dispose()
+            // image.dispose() // we do not dispose our mocked image :)
             nativeBuffer.delete()
           }
         }

Note

I'm doing some tricks to print the preview at 40% of its original size for better debugging However, at the end of the day, this code just simplifies to:

const srcRect = Skia.XYWHRect(0, 0, frameWidth, frameHeight);
const dstRect = Skia.XYWHRect(0, 0, frameWidth, frameHeight);
canvas.drawImageRect(image, srcRect, dstRect, currentPaint);

portrait 0deg

Alt text for Image 1

landscape-left 90deg

landscape-left 90deg

portrait-upside-down 180deg

>portrait-upside-down

landscape-right 270deg

landscape-right

@rodgomesc
Copy link
Contributor

rodgomesc commented Apr 30, 2024

Oh wait, this solves the misalignment of the frame inside the canvas; however, it causes another issue: the image buffer is now rotated 90 degrees clockwise inside a (0 deg portrait) preview for me. I think this is expected since we are lacking orientation support for Android, @mrousavy?

@mrousavy
Copy link
Owner

I am applying the orientation inside the render(..) function, so actually it is expected that the Preview looks correct.

@rodgomesc
Copy link
Contributor

rodgomesc commented Apr 30, 2024

I am applying the orientation inside the render(..) function, so actually it is expected that the Preview looks correct.

i mean, frame.orientation will always returns landscape-right since orientation support not being complete on android, and in render you are just doing getRotationDegrees(frame.orientation)
so i can't visualize the full picture here, of how did you got the image buffers always in portrait without a counter-clockwise rotation of -90deg in the current code that's running in render() {...}

public Orientation getOrientation() throws FrameInvalidError {
assertIsValid();
int degrees = imageProxy.getImageInfo().getRotationDegrees();
return Orientation.Companion.fromRotationDegrees(degrees);
}

@mrousavy
Copy link
Owner

i mean, frame.orientation will always returns landscape-right since orientation support not being complete on android,

no, frame.orientation is an exception here - this is the sensor relative orientation of the ImageProxy, so this is correct as it is.

@pweglik
Copy link

pweglik commented May 8, 2024

Any news on this? I'm also experiencing this misplacement. I observed that it depends on format and video resolution:

  1. Without passing format props to Camera:
  1. Default format got from useCasmeraFormat():
  const format = useCameraFormat(device, []);
  console.log(format);

Log:

{"autoFocusSystem": "contrast-detection", "fieldOfView": 80.98175244802063, "maxFps": 30, "maxISO": 3200, "minFps": 7, "minISO": 100, "photoHeight": 2160, "photoWidth": 4096, "supportsDepthCapture": false, "supportsPhotoHdr": false, "supportsVideoHdr": false, "videoHeight": 2160, "videoStabilizationModes": ["off", "cinematic"], "videoWidth": 3840}

const format = useCameraFormat(device, [
    { videoResolution: { width: 600, height: 400 } },
  ]);
  console.log(format);

Log:

{"autoFocusSystem": "contrast-detection", "fieldOfView": 80.98175244802063, "maxFps": 30, "maxISO": 3200, "minFps": 7, "minISO": 100, "photoHeight": 2160, "photoWidth": 4096, "supportsDepthCapture": false, "supportsPhotoHdr": false, "supportsVideoHdr": false, "videoHeight": 480, "videoStabilizationModes": ["off", "cinematic"], "videoWidth": 640}
const format = useCameraFormat(device, [
    { videoResolution: { width: 1200, height: 800 } },
  ]);
  console.log(format);

Log:

{"autoFocusSystem": "contrast-detection", "fieldOfView": 80.98175244802063, "maxFps": 30, "maxISO": 3200, "minFps": 7, "minISO": 100, "photoHeight": 2160, "photoWidth": 4096, "supportsDepthCapture": false, "supportsPhotoHdr": false, "supportsVideoHdr": false, "videoHeight": 720, "videoStabilizationModes": ["off", "cinematic"], "videoWidth": 1280}
![2024-05-08 10 01 42]()

So in case of lower resolutions (default or set manually) only bottom part is cut off, while in higher resolutions entire frame is misplacement. Maybe that somehow helps you

@OleksiiZdaly
Copy link

袆 薪芯胁懈薪懈 蟹 褑褜芯谐芯 锌褉懈胁芯写褍? 携 褌邪泻芯卸 胁褨写褔褍胁邪褞 褑械 薪械锌褉邪胁懈谢褜薪械 褉芯蟹褌邪褕褍胁邪薪薪褟. 携 锌芯屑褨褌懈胁, 褖芯 褑械 蟹邪谢械卸懈褌褜 胁褨写 褎芯褉屑邪褌褍 褌邪 褉芯蟹写褨谢褜薪芯褩 蟹写邪褌薪芯褋褌褨 胁褨写械芯:

  1. 袘械蟹 锌械褉械写邪褔褨 锌邪褉邪屑械褌褉褨胁 褎芯褉屑邪褌褍 胁 泻邪屑械褉褍:
2. 肖芯褉屑邪褌 蟹邪 蟹邪屑芯胁褔褍胁邪薪薪褟屑 芯褌褉懈屑邪薪芯 蟹 `useCasmeraFormat()`:
  const format = useCameraFormat(device, []);
  console.log(format);

袞褍褉薪邪谢:

{"autoFocusSystem": "contrast-detection", "fieldOfView": 80.98175244802063, "maxFps": 30, "maxISO": 3200, "minFps": 7, "minISO": 100, "photoHeight": 2160, "photoWidth": 4096, "supportsDepthCapture": false, "supportsPhotoHdr": false, "supportsVideoHdr": false, "videoHeight": 2160, "videoStabilizationModes": ["off", "cinematic"], "videoWidth": 3840}
const format = useCameraFormat(device, [
    { videoResolution: { width: 600, height: 400 } },
  ]);
  console.log(format);

袞褍褉薪邪谢:

{"autoFocusSystem": "contrast-detection", "fieldOfView": 80.98175244802063, "maxFps": 30, "maxISO": 3200, "minFps": 7, "minISO": 100, "photoHeight": 2160, "photoWidth": 4096, "supportsDepthCapture": false, "supportsPhotoHdr": false, "supportsVideoHdr": false, "videoHeight": 480, "videoStabilizationModes": ["off", "cinematic"], "videoWidth": 640}
const format = useCameraFormat(device, [
    { videoResolution: { width: 1200, height: 800 } },
  ]);
  console.log(format);

袞褍褉薪邪谢:

{"autoFocusSystem": "contrast-detection", "fieldOfView": 80.98175244802063, "maxFps": 30, "maxISO": 3200, "minFps": 7, "minISO": 100, "photoHeight": 2160, "photoWidth": 4096, "supportsDepthCapture": false, "supportsPhotoHdr": false, "supportsVideoHdr": false, "videoHeight": 720, "videoStabilizationModes": ["off", "cinematic"], "videoWidth": 1280}

2024-05-08 10 01 42
孝邪泻懈屑 褔懈薪芯屑, 褍 褉邪蟹褨 薪懈卸褔芯褩 褉芯蟹写褨谢褜薪芯褩 蟹写邪褌薪芯褋褌褨 (蟹邪 蟹邪屑芯胁褔褍胁邪薪薪褟屑 邪斜芯 胁褋褌邪薪芯胁谢械薪芯褩 胁褉褍褔薪褍) 芯斜褉褨蟹邪褦褌褜褋褟 谢懈褕械 薪懈卸薪褟 褔邪褋褌懈薪邪, 褌芯写褨 褟泻 褍 胁懈褖褨泄 褉芯蟹写褨谢褜薪褨泄 蟹写邪褌薪芯褋褌褨 蟹屑褨褖褍褦褌褜褋褟 胁械褋褜 泻邪写褉. 袦芯卸谢懈胁芯, 褑械 褟泻芯褋褜 褌芯斜褨 写芯锌芯屑芯卸械

For some reason, the format parameters don't change anything for me, the behavior is the same as yours in the second example. And the same problem with useSkiaFrameProcessor. On the emulator, the screen is completely black, and on real devices, part of it is. Another strange thing is that the frame itself processes frames rotated 90 degrees, I noticed this when my model started drawing rectangle around object on a black screen turned to the horizontal position. In IOS all ok.

@mrousavy
Copy link
Owner

Another strange thing is that the frame itself processes frames rotated 90 degrees

That's because of how Cameras work. Sensors are in 90deg orientation. See frame.orientation.

@tomerh2001
Copy link

tomerh2001 commented May 29, 2024

I also encountered this when using useSkiaFrameProcessor:

Screenshot_20240529-163510.png

but useFrameProcessor works normally.

@EnzoDomingues
Copy link
Contributor

EnzoDomingues commented Jun 2, 2024

I just changed this line

-              canvas.rotate(rotation, frame.width / 2, frame.height / 2)
+              canvas.rotate(rotation, frame.width / 2, frame.height / 1.5)

on useSkiaFrameProcessor.ts
Not it's working as expected
Screenshot 2024-06-02 at 15 05 34

@mrousavy
Copy link
Owner

mrousavy commented Jun 3, 2024

Created a fix for this here; #2931

Can you guys test if this works for you? :)

@EnzoDomingues
Copy link
Contributor

EnzoDomingues commented Jun 3, 2024

Created a fix for this here; #2931

Can you guys test if this works for you? :)

Screenshot_20240603-121255

@mrousavy
Hey, for me, it's even worse
I'm using Moto G84

@EnzoDomingues
Copy link
Contributor

EnzoDomingues commented Jun 3, 2024

@mrousavy
I was using this format before

const format = useCameraFormat(device, [
    {
      videoResolution: {
        width: 480,
        height: Platform.OS === 'ios' ? 640 : 720,
      },
    },
  ]);

Using this format, even the colors are changing

Screenshot_20240603-122303

@rodgomesc
Copy link
Contributor

@EnzoDomingues can you leave the format, aspect ration configurations at the default or use them exactly like in this example app and send a new screenshot?

@EnzoDomingues
Copy link
Contributor

@EnzoDomingues can you leave the format, aspect ration configurations at the default or use them exactly like in this example app and send a new screenshot?

The first screenshot is default, without any format

@mrousavy
Copy link
Owner

mrousavy commented Jun 3, 2024

Hm. Matrixes are really hard to get right.

@rodgomesc
Copy link
Contributor

@EnzoDomingues can you leave the format, aspect ration configurations at the default or use them exactly like in this example app and send a new screenshot?

The first screenshot is default, without any format

yup, i can confirm, it's worse for me as well

image

@rodgomesc
Copy link
Contributor

@EnzoDomingues can you confirm if applying this patch #2762 (comment) the frame keeps centered for you?

@mrousavy
Copy link
Owner

mrousavy commented Jun 4, 2024

Try again now - I think it now works :)

@EnzoDomingues
Copy link
Contributor

@mrousavy I added your changes locally and the video is not showing anymore

Screenshot_20240604-133742

@EnzoDomingues
Copy link
Contributor

@EnzoDomingues can you confirm if applying this patch #2762 (comment) the frame keeps centered for you?

@rodgomesc, with your patch
Screenshot_20240604-140530

@mrousavy
Copy link
Owner

mrousavy commented Jun 4, 2024

@EnzoDomingues can you log frame.orientation?

@EnzoDomingues
Copy link
Contributor

@mrousavy sure

 LOG  landscape-right
 LOG  landscape-right
 LOG  landscape-right

@mrousavy
Copy link
Owner

mrousavy commented Jun 4, 2024

Ah that's why! Can you try to adjust this code here:

case 'landscape-right':
// rotate two flips on (0,0) origin and move X + Y into view again
canvas.translate(frame.height, frame.width)
canvas.translate(270, 0)

I don't have a phone where it's sensor orientation is landscape-right, so I cannot test this myself. Maybe just switch out the translations, use 0 or whatever - if you find a fix pls let me know.

@mrousavy
Copy link
Owner

mrousavy commented Jun 4, 2024

oh lol after posting this I just realized I used the wrong function.... lmao see 38c6983

now that should work

@EnzoDomingues
Copy link
Contributor

EnzoDomingues commented Jun 4, 2024

@mrousavy

   // rotate two flips on (0,0) origin and move X + Y into view again
        canvas.translate(0, frame.width)
        canvas.rotate(270, 0, 0)

This works for me instead canvas.translate(frame.height, frame.width)
Could you update it?
Screenshot_20240604-144327

@mrousavy
Copy link
Owner

mrousavy commented Jun 4, 2024

aha interesting... can you send a PR?

@EnzoDomingues
Copy link
Contributor

@mrousavy MR created :)

@tomerh2001
Copy link

Is there a release containing this fix?

@mrousavy
Copy link
Owner

mrousavy commented Jun 5, 2024

Not yet.

@tomerh2001
Copy link

Not yet.

Waiting on this for prod release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
馃悰 bug Something isn't working
Projects
None yet
10 participants