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

fix: Move Frame into VisionCamera Core (and change namespace) #3079

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ using namespace facebook;
using namespace jni;

struct JFrame : public JavaClass<JFrame> {
static constexpr auto kJavaDescriptor = "Lcom/mrousavy/camera/frameprocessors/Frame;";
static constexpr auto kJavaDescriptor = "Lcom/mrousavy/camera/core/Frame;";

public:
int getWidth() const;
Expand Down
141 changes: 141 additions & 0 deletions package/android/src/main/java/com/mrousavy/camera/core/Frame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.mrousavy.camera.core

import android.hardware.HardwareBuffer
import android.media.Image
import android.os.Build
import androidx.annotation.OptIn
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageProxy
import com.facebook.proguard.annotations.DoNotStrip
import com.mrousavy.camera.core.types.Orientation
import com.mrousavy.camera.core.types.Orientation.Companion.fromRotationDegrees
import com.mrousavy.camera.core.types.PixelFormat
import com.mrousavy.camera.core.types.PixelFormat.Companion.fromImageFormat

class Frame(private val _imageProxy: ImageProxy) {
private var refCount = 0

private fun assertIsValid() {
if (!getIsImageValid(imageProxy)) {
throw FrameInvalidError()
}
}

@Suppress("MemberVisibilityCanBePrivate")
val imageProxy: ImageProxy
get() {
assertIsValid()
return _imageProxy
}

@get:OptIn(ExperimentalGetImage::class)
val image: Image
get() = imageProxy.image ?: throw FrameInvalidError()

@get:DoNotStrip
@Suppress("unused")
val width: Int
get() = imageProxy.width

@get:DoNotStrip
@Suppress("unused")
val height: Int
get() = imageProxy.height

@get:DoNotStrip
@Suppress("unused")
val isValid: Boolean
get() = getIsImageValid(imageProxy)

@get:DoNotStrip
@Suppress("unused")
val isMirrored: Boolean
get() {
val matrix = imageProxy.imageInfo.sensorToBufferTransformMatrix
val values = FloatArray(9)
matrix.getValues(values)
// Check if the X scale factor is negative, indicating a horizontal flip.
return values[0] < 0
}

@get:DoNotStrip
@Suppress("unused")
val timestamp: Long
get() = imageProxy.imageInfo.timestamp

@get:DoNotStrip
@Suppress("unused")
val orientation: Orientation
get() {
val degrees = imageProxy.imageInfo.rotationDegrees
val orientation = fromRotationDegrees(degrees)
// .rotationDegrees is the rotation that needs to be applied to make the image appear
// upright. Our orientation is the actual orientation of the Frame, so the opposite. Reverse it.
return orientation.reversed()
}

@get:DoNotStrip
@Suppress("unused")
val pixelFormat: PixelFormat
get() = fromImageFormat(imageProxy.format)

@get:DoNotStrip
@Suppress("unused")
val planesCount: Int
get() = imageProxy.planes.size

@get:DoNotStrip
@Suppress("unused")
val bytesPerRow: Int
get() = imageProxy.planes[0].rowStride

@get:DoNotStrip
@Suppress("unused")
private val hardwareBufferBoxed: Any?
get() = hardwareBuffer

@Suppress("unused", "MemberVisibilityCanBePrivate")
val hardwareBuffer: HardwareBuffer?
get() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
throw HardwareBuffersNotAvailableError()
}
return image.hardwareBuffer
}

@Synchronized
private fun getIsImageValid(image: ImageProxy): Boolean {
if (refCount <= 0) return false
try {
// will throw an exception if the image is already closed
image.format
// no exception thrown, image must still be valid.
return true
} catch (e: IllegalStateException) {
// exception thrown, image has already been closed.
return false
}
}

@Suppress("unused")
@DoNotStrip
@Synchronized
fun incrementRefCount() {
refCount++
}

@Suppress("unused")
@DoNotStrip
@Synchronized
fun decrementRefCount() {
refCount--
if (refCount <= 0) {
// If no reference is held on this Image, close it.
close()
}
}

private fun close() {
_imageProxy.close()
}
}

This file was deleted.

3 changes: 2 additions & 1 deletion package/ios/Core/CameraSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,8 @@ final class CameraSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegat

if let delegate {
// Call Frame Processor (delegate) for every Video Frame
delegate.onFrame(sampleBuffer: sampleBuffer, orientation: orientation, isMirrored: isMirrored)
let frame = Frame(withBuffer: sampleBuffer, orientation: orientation, isMirrored: isMirrored)
delegate.onFrame(frame: frame)
}
}

Expand Down
2 changes: 1 addition & 1 deletion package/ios/Core/CameraSessionDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protocol CameraSessionDelegate: AnyObject {
/**
Called for every frame (if video or frameProcessor is enabled)
*/
func onFrame(sampleBuffer: CMSampleBuffer, orientation: Orientation, isMirrored: Bool)
func onFrame(frame: Frame)
/**
Called whenever a QR/Barcode has been scanned. Only if the CodeScanner Output is enabled
*/
Expand Down
Loading
Loading