Skip to content
Open
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
25 changes: 15 additions & 10 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def gitCommitCount = getGitCommitCount()

def startTasks = gradle.startParameter.taskNames
def isExplicitNativeBuild = startTasks.any { it.contains("buildNative") }
def prebuiltNativeLibs = fileTree(dir: "src/main/jniLibs", include: "**/libcamera3.so")
def usePrebuiltNativeLibs = !isExplicitNativeBuild && !prebuiltNativeLibs.isEmpty()

android {
flavorDimensions.add("api")
Expand All @@ -55,10 +57,12 @@ android {
versionCode versionCodeOffset + gitCommitCount
versionName "${majorVersion}.${minorVersion}.${patchVersion}"

externalNativeBuild {
cmake {
cppFlags += ""
arguments "-DANDROID_PLATFORM=android-29"
if (!usePrebuiltNativeLibs) {
externalNativeBuild {
cmake {
cppFlags += ""
arguments "-DANDROID_PLATFORM=android-29"
}
}
}

Expand Down Expand Up @@ -95,10 +99,12 @@ android {
jvmTarget = '11'
}

externalNativeBuild {
cmake {
path file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
if (!usePrebuiltNativeLibs) {
externalNativeBuild {
cmake {
path file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
}

Expand All @@ -121,8 +127,7 @@ android {

afterEvaluate {
if (!isExplicitNativeBuild) {
def jniLibTree = fileTree(dir: "src/main/jniLibs", include: "**/libcamera3.so")
if (jniLibTree.isEmpty()) {
if (!usePrebuiltNativeLibs) {
println ">>> [警告] jniLibs 文件不存在, 无法快速编译 <<<"
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.nothing.camera2magic.hook

import android.graphics.ImageFormat
import android.graphics.SurfaceTexture
import android.media.ImageReader
import android.os.Handler
import android.os.HandlerThread
Expand All @@ -11,6 +10,8 @@ import java.util.WeakHashMap

data class BlackHole(
val identityId: Int,
val width: Int,
val height: Int,
val surface: Surface,
val reader: ImageReader
)
Expand All @@ -19,11 +20,11 @@ object BlackHoleMapper {
private val oabMap = WeakHashMap<Surface, BlackHole>()
private val camera3Thread = HandlerThread("camera3Thread").apply { start() }
private val camera3Handler = Handler(camera3Thread.looper)
fun createBlackHole(origin: Surface): Surface {
fun createBlackHole(origin: Surface, width: Int, height: Int): Surface {
return oabMap.getOrPut(origin) {
val id = 20 + oabMap.size
val reader = ImageReader.newInstance(1280, 720,
ImageFormat.YUV_420_888, 2)
val reader = ImageReader.newInstance(width, height,
ImageFormat.PRIVATE, 4)
reader.setOnImageAvailableListener({ r ->
runCatching {
val image = r.acquireLatestImage()
Expand All @@ -32,7 +33,8 @@ object BlackHoleMapper {
Dog.e(TAG, "acquireLatestImage Failed: ${exception.message}", exception, true)
}
}, camera3Handler)
BlackHole(id, reader.surface, reader)
Dog.i(TAG, "blackhole[$id] ${width}x${height}, format=PRIVATE", SourceManager.enableLog)
BlackHole(id, width, height, reader.surface, reader)
}.surface
}

Expand All @@ -46,4 +48,4 @@ object BlackHoleMapper {
}
oabMap.clear()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ object NativeBridge {
synchronized(surfaceLock) {
val lastSurface = lastRegisteredSurface?.get()
state.surface?.let { surface ->
if (!surface.isValid) {
Dog.e(TAG, "Skip invalid surface registration.", null, SourceManager.enableLog)
return
}
if (forceRefresh || surface != lastSurface) {
registerSurface(cameraState = state)
lastRegisteredSurface = WeakReference(surface)
Expand All @@ -84,4 +88,4 @@ object NativeBridge {
lastRegisteredSurface = null
}
}
}
}
45 changes: 43 additions & 2 deletions app/src/main/java/com/nothing/camera2magic/hook/SourceManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import android.content.ContentUris
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.ExifInterface
import android.net.Uri
import android.provider.MediaStore
import com.nothing.camera2magic.GlobalState
import java.io.FileNotFoundException
Expand Down Expand Up @@ -155,6 +158,7 @@ object SourceManager {
val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageId)
val contentResolver = GlobalState.appContext.contentResolver
val result = runCatching {
val exifOrientation = readExifOrientation(uri)
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
contentResolver.openInputStream(uri)?.use {
Expand All @@ -169,10 +173,14 @@ object SourceManager {
val bitmap = contentResolver.openInputStream(uri)?.use { stream ->
BitmapFactory.decodeStream(stream, null, options)
} ?: throw IllegalStateException("无法解码图片")
val orientedBitmap = applyExifOrientation(bitmap, exifOrientation)

try {
NativeBridge.processBitmap(bitmap)
NativeBridge.processBitmap(orientedBitmap)
} finally {
if (orientedBitmap !== bitmap) {
orientedBitmap.recycle()
}
bitmap.recycle()
}
}
Expand All @@ -189,6 +197,39 @@ object SourceManager {
}
}

private fun readExifOrientation(uri: Uri): Int {
val contentResolver = GlobalState.appContext.contentResolver
return runCatching {
contentResolver.openInputStream(uri)?.use { stream ->
ExifInterface(stream).getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL
)
} ?: ExifInterface.ORIENTATION_NORMAL
}.getOrDefault(ExifInterface.ORIENTATION_NORMAL)
}

private fun applyExifOrientation(bitmap: Bitmap, orientation: Int): Bitmap {
val matrix = Matrix()
when (orientation) {
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.postScale(-1f, 1f)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.postScale(1f, -1f)
ExifInterface.ORIENTATION_TRANSPOSE -> {
matrix.postRotate(90f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
ExifInterface.ORIENTATION_TRANSVERSE -> {
matrix.postRotate(-90f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
else -> return bitmap
}
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}

fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val (height: Int, width: Int) = options.outHeight to options.outWidth
var inSampleSize = 1
Expand All @@ -206,4 +247,4 @@ object SourceManager {
mediaIsReady = ready
toastMessage = message
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.nothing.camera2magic.viewmodel

import android.app.Application
import android.content.ContentUris
import android.provider.DocumentsContract
import android.graphics.Bitmap
import android.net.Uri
import android.provider.MediaStore
Expand Down Expand Up @@ -61,9 +62,7 @@ class SpotlightViewModel(

fun onMediaSelected(type: MediaType, uri: Uri?) {
if (uri == null) return
val mediaId = try {
uri.lastPathSegment?.toLongOrNull()
} catch (_: kotlin.Exception) { null }
val mediaId = resolveMediaId(type, uri)
if (mediaId != null) {
saveMediaId(type, mediaId)
loadAndVerifyMedia(type, mediaId)
Expand Down Expand Up @@ -103,6 +102,31 @@ class SpotlightViewModel(
MediaType.IMAGE -> repository.imageId
}
}

private fun resolveMediaId(type: MediaType, uri: Uri): Long? {
uri.lastPathSegment?.toLongOrNull()?.let { return it }

val documentId = runCatching {
if (DocumentsContract.isDocumentUri(app, uri)) {
DocumentsContract.getDocumentId(uri)
} else {
null
}
}.getOrNull()
documentId?.substringAfter(':')?.toLongOrNull()?.let { return it }
documentId?.toLongOrNull()?.let { return it }

val projection = when (type) {
MediaType.VIDEO -> arrayOf(MediaStore.Video.Media._ID)
MediaType.IMAGE -> arrayOf(MediaStore.Images.Media._ID)
}
return runCatching {
app.contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) cursor.getLong(0) else null
}
}.getOrNull()
}

private fun loadAndVerifyMedia(type: MediaType, mediaIdOverride: Long? = null) {
viewModelScope.launch(Dispatchers.IO) {
val mediaId = mediaIdOverride ?: getMediaId(type)
Expand Down Expand Up @@ -143,4 +167,4 @@ class SpotlightViewModel(
currentMap + (type to thumbnail)
}
}
}
}
Loading