mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
current stand
This commit is contained in:
parent
f035c40c21
commit
0dcf106a22
@ -126,4 +126,5 @@ dependencies {
|
|||||||
implementation 'com.maxkeppeler.sheets-compose-dialogs:input:1.2.0'
|
implementation 'com.maxkeppeler.sheets-compose-dialogs:input:1.2.0'
|
||||||
|
|
||||||
implementation 'io.github.ujizin:camposer:0.1.0'
|
implementation 'io.github.ujizin:camposer:0.1.0'
|
||||||
|
implementation "com.github.skydoves:cloudy:0.1.2"
|
||||||
}
|
}
|
@ -2,26 +2,35 @@ package app.myzel394.alibi
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.graphics.ImageFormat
|
import android.graphics.ImageFormat
|
||||||
import android.hardware.camera2.CameraCaptureSession
|
import android.hardware.camera2.CameraCaptureSession
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
import android.hardware.camera2.CameraDevice
|
import android.hardware.camera2.CameraDevice
|
||||||
import android.hardware.camera2.CameraManager
|
import android.hardware.camera2.CameraManager
|
||||||
import android.media.ImageReader
|
import android.media.ImageReader
|
||||||
import android.net.wifi.aware.Characteristics
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
|
import android.renderscript.Allocation
|
||||||
|
import android.renderscript.Element
|
||||||
|
import android.renderscript.RenderScript
|
||||||
|
import android.renderscript.ScriptIntrinsicYuvToRGB
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Size
|
import android.util.Size
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat.getSystemService
|
import androidx.core.content.ContextCompat.getSystemService
|
||||||
|
import app.myzel394.alibi.ui.utils.fastblur
|
||||||
|
import app.myzel394.alibi.ui.utils.getScreenSize
|
||||||
|
import app.myzel394.alibi.ui.utils.imageToByteBuffer
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
|
|
||||||
class CameraHandler(
|
class CameraHandler(
|
||||||
private val manager: CameraManager,
|
private val manager: CameraManager,
|
||||||
private val device: CameraDevice,
|
private val device: CameraDevice,
|
||||||
@ -65,7 +74,87 @@ class CameraHandler(
|
|||||||
val captureRequest = device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
|
val captureRequest = device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
|
||||||
captureRequest.addTarget(surface)
|
captureRequest.addTarget(surface)
|
||||||
|
|
||||||
session.setRepeatingRequest(captureRequest.build(), null, handler)
|
session.setRepeatingRequest(
|
||||||
|
captureRequest.build(),
|
||||||
|
null,
|
||||||
|
handler,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.P)
|
||||||
|
suspend fun startBlurredPreview(
|
||||||
|
surface: Surface,
|
||||||
|
context: Context,
|
||||||
|
size: Size? = null,
|
||||||
|
) {
|
||||||
|
val readerSize = size ?: getScreenSize(context).let {
|
||||||
|
Size(
|
||||||
|
it.width / 10,
|
||||||
|
it.height / 10,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val imageReader = ImageReader.newInstance(
|
||||||
|
readerSize.width,
|
||||||
|
readerSize.height,
|
||||||
|
ImageFormat.YUV_420_888,
|
||||||
|
IMAGE_BUFFER_SIZE,
|
||||||
|
)
|
||||||
|
imageReader.setOnImageAvailableListener({ reader ->
|
||||||
|
val image = reader.acquireLatestImage() ?: return@setOnImageAvailableListener
|
||||||
|
|
||||||
|
val yuvBytes: ByteBuffer = imageToByteBuffer(image)
|
||||||
|
|
||||||
|
// Convert YUV to RGB
|
||||||
|
val rs = RenderScript.create(context)
|
||||||
|
|
||||||
|
val bitmap = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888)
|
||||||
|
val allocationRgb = Allocation.createFromBitmap(rs, bitmap)
|
||||||
|
|
||||||
|
val allocationYuv = Allocation.createSized(rs, Element.U8(rs), yuvBytes.array().size)
|
||||||
|
allocationYuv.copyFrom(yuvBytes.array())
|
||||||
|
|
||||||
|
val scriptYuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
|
||||||
|
scriptYuvToRgb.setInput(allocationYuv)
|
||||||
|
scriptYuvToRgb.forEach(allocationRgb)
|
||||||
|
|
||||||
|
allocationRgb.copyTo(bitmap)
|
||||||
|
|
||||||
|
// Rotate bitmap
|
||||||
|
val matrix = android.graphics.Matrix()
|
||||||
|
matrix.postRotate(90f)
|
||||||
|
val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
||||||
|
|
||||||
|
val blurredBitmap = fastblur(rotatedBitmap, 1f, 2)
|
||||||
|
|
||||||
|
// Send blurredBitmap to `surface`
|
||||||
|
val canvas = surface.lockCanvas(null)
|
||||||
|
canvas.drawBitmap(blurredBitmap, 0f, 0f, null)
|
||||||
|
surface.unlockCanvasAndPost(canvas)
|
||||||
|
|
||||||
|
// Destroy
|
||||||
|
allocationRgb.destroy()
|
||||||
|
allocationYuv.destroy()
|
||||||
|
scriptYuvToRgb.destroy()
|
||||||
|
rs.destroy()
|
||||||
|
bitmap.recycle()
|
||||||
|
rotatedBitmap.recycle()
|
||||||
|
blurredBitmap.recycle()
|
||||||
|
|
||||||
|
// Release
|
||||||
|
image.close()
|
||||||
|
}, handler)
|
||||||
|
|
||||||
|
val outputs = listOf(imageReader.surface)
|
||||||
|
val session = createCaptureSession(outputs)
|
||||||
|
|
||||||
|
val captureRequest = device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
|
||||||
|
captureRequest.addTarget(imageReader.surface)
|
||||||
|
|
||||||
|
session.setRepeatingRequest(
|
||||||
|
captureRequest.build(),
|
||||||
|
null,
|
||||||
|
handler,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopPreview() {
|
fun stopPreview() {
|
||||||
@ -160,7 +249,7 @@ class CameraHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val IMAGE_BUFFER_SIZE = 3
|
const val IMAGE_BUFFER_SIZE = 2
|
||||||
|
|
||||||
fun getCameraManager(
|
fun getCameraManager(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -1,29 +1,23 @@
|
|||||||
package app.myzel394.alibi.ui.screens
|
package app.myzel394.alibi.ui.screens
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Point
|
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
import android.hardware.display.DisplayManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Size
|
import android.util.Size
|
||||||
import android.view.Display
|
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import app.myzel394.alibi.CameraHandler
|
import app.myzel394.alibi.CameraHandler
|
||||||
@ -31,6 +25,7 @@ import app.myzel394.alibi.ui.models.AudioRecorderModel
|
|||||||
import app.myzel394.alibi.ui.models.VideoRecorderModel
|
import app.myzel394.alibi.ui.models.VideoRecorderModel
|
||||||
import app.myzel394.alibi.ui.utils.ChangeNavColors
|
import app.myzel394.alibi.ui.utils.ChangeNavColors
|
||||||
import app.myzel394.alibi.ui.utils.getOptimalPreviewSize
|
import app.myzel394.alibi.ui.utils.getOptimalPreviewSize
|
||||||
|
import app.myzel394.alibi.ui.utils.rememberScreenSize
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.Float.max
|
import java.lang.Float.max
|
||||||
@ -58,64 +53,26 @@ fun RecorderScreen(
|
|||||||
} else {
|
} else {
|
||||||
var scaleValue by remember { mutableFloatStateOf(1f) }
|
var scaleValue by remember { mutableFloatStateOf(1f) }
|
||||||
|
|
||||||
val screenSize = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
println("scaleValue: $scaleValue")
|
||||||
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
||||||
val bounds = windowManager.currentWindowMetrics.bounds
|
|
||||||
|
|
||||||
Size(bounds.width(), bounds.height())
|
val screenSize = rememberScreenSize()
|
||||||
} else {
|
val previewSize = Size(
|
||||||
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
screenSize.width / 10,
|
||||||
val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
|
screenSize.height / 10,
|
||||||
|
)
|
||||||
|
|
||||||
val size = Point()
|
|
||||||
display.getRealSize(size)
|
|
||||||
|
|
||||||
Size(size.x, size.y)
|
|
||||||
}
|
|
||||||
|
|
||||||
ChangeNavColors(color = Color.Transparent)
|
ChangeNavColors(color = Color.Transparent)
|
||||||
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize(),
|
||||||
.scale(scaleValue),
|
|
||||||
factory = { context ->
|
factory = { context ->
|
||||||
val surface = object : SurfaceView(context) {
|
val surface = object : SurfaceView(context) {
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
val width = resolveSize(suggestedMinimumWidth, widthMeasureSpec);
|
setMeasuredDimension(
|
||||||
val height = resolveSize(suggestedMinimumHeight, heightMeasureSpec);
|
screenSize.width,
|
||||||
|
screenSize.height,
|
||||||
val supportedPreviewSizes = camera!!
|
)
|
||||||
.characteristics
|
|
||||||
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
|
||||||
.getOutputSizes(SurfaceHolder::class.java)
|
|
||||||
|
|
||||||
// Make sure preview is in `cover` mode and not `contain` mode
|
|
||||||
|
|
||||||
if (supportedPreviewSizes != null) {
|
|
||||||
val previewSize = getOptimalPreviewSize(supportedPreviewSizes, width, height);
|
|
||||||
|
|
||||||
val ratio = if (previewSize.height >= previewSize.width)
|
|
||||||
(previewSize.height / previewSize.width).toFloat()
|
|
||||||
else (previewSize.width / previewSize.height).toFloat()
|
|
||||||
|
|
||||||
val optimalWidth = width
|
|
||||||
val optimalHeight = (width * ratio).toInt()
|
|
||||||
|
|
||||||
// Make sure the camera preview uses the whole screen
|
|
||||||
val widthScaleRatio = optimalWidth.toFloat() / previewSize.width
|
|
||||||
val heightScaleUpRatio = optimalHeight.toFloat() / previewSize.height
|
|
||||||
|
|
||||||
setMeasuredDimension(
|
|
||||||
(previewSize.width * widthScaleRatio).toInt(),
|
|
||||||
(previewSize.height * heightScaleUpRatio).toInt()
|
|
||||||
)
|
|
||||||
|
|
||||||
scaleValue = max(
|
|
||||||
screenSize.width / optimalWidth.toFloat(),
|
|
||||||
screenSize.height / optimalHeight.toFloat(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,27 +83,36 @@ fun RecorderScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
|
surfaceView.holder.apply {
|
||||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
addCallback(object : SurfaceHolder.Callback {
|
||||||
scope.launch {
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||||
camera!!.startPreview(holder.surface)
|
scope.launch {
|
||||||
|
camera!!.startBlurredPreview(
|
||||||
|
holder.surface,
|
||||||
|
context,
|
||||||
|
previewSize,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun surfaceChanged(
|
override fun surfaceChanged(
|
||||||
holder: SurfaceHolder,
|
holder: SurfaceHolder,
|
||||||
format: Int,
|
format: Int,
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int
|
height: Int
|
||||||
) {
|
) {
|
||||||
}
|
println("surfaceChanged")
|
||||||
|
println("width: $width, height: $height")
|
||||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
|
||||||
scope.launch {
|
|
||||||
camera!!.stopPreview()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
|
scope.launch {
|
||||||
|
camera!!.stopPreview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setFixedSize(previewSize.width, previewSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
surfaceView
|
surfaceView
|
||||||
}
|
}
|
||||||
@ -165,6 +131,13 @@ fun RecorderScreen(
|
|||||||
}) {
|
}) {
|
||||||
Text("Take photo")
|
Text("Take photo")
|
||||||
}
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
package app.myzel394.alibi.ui.utils
|
package app.myzel394.alibi.ui.utils
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.ImageFormat
|
||||||
|
import android.graphics.Rect
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
|
import android.media.Image
|
||||||
import android.util.Size
|
import android.util.Size
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes rotation required to transform the camera sensor output orientation to the
|
* Computes rotation required to transform the camera sensor output orientation to the
|
||||||
* device's current orientation in degrees.
|
* device's current orientation in degrees.
|
||||||
@ -54,4 +60,293 @@ fun getOptimalPreviewSize(sizes: Array<Size>, w: Int, h: Int): Size {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return optimalSize!!
|
return optimalSize!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/55544614/9878135
|
||||||
|
fun imageToByteBuffer(image: Image): ByteBuffer {
|
||||||
|
val crop: Rect = image.cropRect
|
||||||
|
val width: Int = crop.width()
|
||||||
|
val height: Int = crop.height()
|
||||||
|
val planes = image.planes
|
||||||
|
val rowData = ByteArray(planes[0].rowStride)
|
||||||
|
val bufferSize = width * height * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8
|
||||||
|
val output = ByteBuffer.allocateDirect(bufferSize)
|
||||||
|
var channelOffset = 0
|
||||||
|
var outputStride = 0
|
||||||
|
for (planeIndex in 0..2) {
|
||||||
|
if (planeIndex == 0) {
|
||||||
|
channelOffset = 0
|
||||||
|
outputStride = 1
|
||||||
|
} else if (planeIndex == 1) {
|
||||||
|
channelOffset = width * height + 1
|
||||||
|
outputStride = 2
|
||||||
|
} else if (planeIndex == 2) {
|
||||||
|
channelOffset = width * height
|
||||||
|
outputStride = 2
|
||||||
|
}
|
||||||
|
val buffer = planes[planeIndex].buffer
|
||||||
|
val rowStride = planes[planeIndex].rowStride
|
||||||
|
val pixelStride = planes[planeIndex].pixelStride
|
||||||
|
val shift = if (planeIndex == 0) 0 else 1
|
||||||
|
val widthShifted = width shr shift
|
||||||
|
val heightShifted = height shr shift
|
||||||
|
buffer.position(rowStride * (crop.top shr shift) + pixelStride * (crop.left shr shift))
|
||||||
|
for (row in 0 until heightShifted) {
|
||||||
|
val length: Int
|
||||||
|
if (pixelStride == 1 && outputStride == 1) {
|
||||||
|
length = widthShifted
|
||||||
|
buffer[output.array(), channelOffset, length]
|
||||||
|
channelOffset += length
|
||||||
|
} else {
|
||||||
|
length = (widthShifted - 1) * pixelStride + 1
|
||||||
|
buffer[rowData, 0, length]
|
||||||
|
for (col in 0 until widthShifted) {
|
||||||
|
output.array()[channelOffset] = rowData[col * pixelStride]
|
||||||
|
channelOffset += outputStride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row < heightShifted - 1) {
|
||||||
|
buffer.position(buffer.position() + rowStride - length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The following code is from https://stackoverflow.com/a/10028267/9878135
|
||||||
|
/**
|
||||||
|
* Stack Blur v1.0 from
|
||||||
|
* http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
|
||||||
|
* Java Author: Mario Klingemann <mario at quasimondo.com>
|
||||||
|
* http://incubator.quasimondo.com
|
||||||
|
*
|
||||||
|
* created Feburary 29, 2004
|
||||||
|
* Android port : Yahel Bouaziz <yahel at kayenko.com>
|
||||||
|
* http://www.kayenko.com
|
||||||
|
* ported april 5th, 2012
|
||||||
|
*
|
||||||
|
* This is a compromise between Gaussian Blur and Box blur
|
||||||
|
* It creates much better looking blurs than Box Blur, but is
|
||||||
|
* 7x faster than my Gaussian Blur implementation.
|
||||||
|
*
|
||||||
|
* I called it Stack Blur because this describes best how this
|
||||||
|
* filter works internally: it creates a kind of moving stack
|
||||||
|
* of colors whilst scanning through the image. Thereby it
|
||||||
|
* just has to add one new block of color to the right side
|
||||||
|
* of the stack and remove the leftmost color. The remaining
|
||||||
|
* colors on the topmost layer of the stack are either added on
|
||||||
|
* or reduced by one, depending on if they are on the right or
|
||||||
|
* on the left side of the stack.
|
||||||
|
*
|
||||||
|
* If you are using this algorithm in your code please add
|
||||||
|
* the following line:
|
||||||
|
* Stack Blur Algorithm by Mario Klingemann <mario></mario>@quasimondo.com>
|
||||||
|
</yahel></mario> */
|
||||||
|
fun fastblur(sentBitmap: Bitmap, scale: Float, radius: Int): Bitmap {
|
||||||
|
assert(radius >= 1) { "radius must be >= 1" }
|
||||||
|
|
||||||
|
var sentBitmap = sentBitmap
|
||||||
|
val width = Math.round(sentBitmap.width * scale)
|
||||||
|
val height = Math.round(sentBitmap.height * scale)
|
||||||
|
sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false)
|
||||||
|
val bitmap = sentBitmap.copy(sentBitmap.config, true)
|
||||||
|
val w = bitmap.width
|
||||||
|
val h = bitmap.height
|
||||||
|
val pix = IntArray(w * h)
|
||||||
|
bitmap.getPixels(pix, 0, w, 0, 0, w, h)
|
||||||
|
val wm = w - 1
|
||||||
|
val hm = h - 1
|
||||||
|
val wh = w * h
|
||||||
|
val div = radius + radius + 1
|
||||||
|
val r = IntArray(wh)
|
||||||
|
val g = IntArray(wh)
|
||||||
|
val b = IntArray(wh)
|
||||||
|
var rsum: Int
|
||||||
|
var gsum: Int
|
||||||
|
var bsum: Int
|
||||||
|
var x: Int
|
||||||
|
var y: Int
|
||||||
|
var i: Int
|
||||||
|
var p: Int
|
||||||
|
var yp: Int
|
||||||
|
var yi: Int
|
||||||
|
var yw: Int
|
||||||
|
val vmin = IntArray(Math.max(w, h))
|
||||||
|
var divsum = div + 1 shr 1
|
||||||
|
divsum *= divsum
|
||||||
|
val dv = IntArray(256 * divsum)
|
||||||
|
i = 0
|
||||||
|
while (i < 256 * divsum) {
|
||||||
|
dv[i] = i / divsum
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
yi = 0
|
||||||
|
yw = yi
|
||||||
|
val stack = Array(div) { IntArray(3) }
|
||||||
|
var stackpointer: Int
|
||||||
|
var stackstart: Int
|
||||||
|
var sir: IntArray
|
||||||
|
var rbs: Int
|
||||||
|
val r1 = radius + 1
|
||||||
|
var routsum: Int
|
||||||
|
var goutsum: Int
|
||||||
|
var boutsum: Int
|
||||||
|
var rinsum: Int
|
||||||
|
var ginsum: Int
|
||||||
|
var binsum: Int
|
||||||
|
y = 0
|
||||||
|
while (y < h) {
|
||||||
|
bsum = 0
|
||||||
|
gsum = bsum
|
||||||
|
rsum = gsum
|
||||||
|
boutsum = rsum
|
||||||
|
goutsum = boutsum
|
||||||
|
routsum = goutsum
|
||||||
|
binsum = routsum
|
||||||
|
ginsum = binsum
|
||||||
|
rinsum = ginsum
|
||||||
|
i = -radius
|
||||||
|
while (i <= radius) {
|
||||||
|
p = pix[yi + Math.min(wm, Math.max(i, 0))]
|
||||||
|
sir = stack[i + radius]
|
||||||
|
sir[0] = p and 0xff0000 shr 16
|
||||||
|
sir[1] = p and 0x00ff00 shr 8
|
||||||
|
sir[2] = p and 0x0000ff
|
||||||
|
rbs = r1 - Math.abs(i)
|
||||||
|
rsum += sir[0] * rbs
|
||||||
|
gsum += sir[1] * rbs
|
||||||
|
bsum += sir[2] * rbs
|
||||||
|
if (i > 0) {
|
||||||
|
rinsum += sir[0]
|
||||||
|
ginsum += sir[1]
|
||||||
|
binsum += sir[2]
|
||||||
|
} else {
|
||||||
|
routsum += sir[0]
|
||||||
|
goutsum += sir[1]
|
||||||
|
boutsum += sir[2]
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
stackpointer = radius
|
||||||
|
x = 0
|
||||||
|
while (x < w) {
|
||||||
|
r[yi] = dv[rsum]
|
||||||
|
g[yi] = dv[gsum]
|
||||||
|
b[yi] = dv[bsum]
|
||||||
|
rsum -= routsum
|
||||||
|
gsum -= goutsum
|
||||||
|
bsum -= boutsum
|
||||||
|
stackstart = stackpointer - radius + div
|
||||||
|
sir = stack[stackstart % div]
|
||||||
|
routsum -= sir[0]
|
||||||
|
goutsum -= sir[1]
|
||||||
|
boutsum -= sir[2]
|
||||||
|
if (y == 0) {
|
||||||
|
vmin[x] = Math.min(x + radius + 1, wm)
|
||||||
|
}
|
||||||
|
p = pix[yw + vmin[x]]
|
||||||
|
sir[0] = p and 0xff0000 shr 16
|
||||||
|
sir[1] = p and 0x00ff00 shr 8
|
||||||
|
sir[2] = p and 0x0000ff
|
||||||
|
rinsum += sir[0]
|
||||||
|
ginsum += sir[1]
|
||||||
|
binsum += sir[2]
|
||||||
|
rsum += rinsum
|
||||||
|
gsum += ginsum
|
||||||
|
bsum += binsum
|
||||||
|
stackpointer = (stackpointer + 1) % div
|
||||||
|
sir = stack[stackpointer % div]
|
||||||
|
routsum += sir[0]
|
||||||
|
goutsum += sir[1]
|
||||||
|
boutsum += sir[2]
|
||||||
|
rinsum -= sir[0]
|
||||||
|
ginsum -= sir[1]
|
||||||
|
binsum -= sir[2]
|
||||||
|
yi++
|
||||||
|
x++
|
||||||
|
}
|
||||||
|
yw += w
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
x = 0
|
||||||
|
while (x < w) {
|
||||||
|
bsum = 0
|
||||||
|
gsum = bsum
|
||||||
|
rsum = gsum
|
||||||
|
boutsum = rsum
|
||||||
|
goutsum = boutsum
|
||||||
|
routsum = goutsum
|
||||||
|
binsum = routsum
|
||||||
|
ginsum = binsum
|
||||||
|
rinsum = ginsum
|
||||||
|
yp = -radius * w
|
||||||
|
i = -radius
|
||||||
|
while (i <= radius) {
|
||||||
|
yi = Math.max(0, yp) + x
|
||||||
|
sir = stack[i + radius]
|
||||||
|
sir[0] = r[yi]
|
||||||
|
sir[1] = g[yi]
|
||||||
|
sir[2] = b[yi]
|
||||||
|
rbs = r1 - Math.abs(i)
|
||||||
|
rsum += r[yi] * rbs
|
||||||
|
gsum += g[yi] * rbs
|
||||||
|
bsum += b[yi] * rbs
|
||||||
|
if (i > 0) {
|
||||||
|
rinsum += sir[0]
|
||||||
|
ginsum += sir[1]
|
||||||
|
binsum += sir[2]
|
||||||
|
} else {
|
||||||
|
routsum += sir[0]
|
||||||
|
goutsum += sir[1]
|
||||||
|
boutsum += sir[2]
|
||||||
|
}
|
||||||
|
if (i < hm) {
|
||||||
|
yp += w
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
yi = x
|
||||||
|
stackpointer = radius
|
||||||
|
y = 0
|
||||||
|
while (y < h) {
|
||||||
|
|
||||||
|
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
|
||||||
|
pix[yi] = -0x1000000 and pix[yi] or (dv[rsum] shl 16) or (dv[gsum] shl 8) or dv[bsum]
|
||||||
|
rsum -= routsum
|
||||||
|
gsum -= goutsum
|
||||||
|
bsum -= boutsum
|
||||||
|
stackstart = stackpointer - radius + div
|
||||||
|
sir = stack[stackstart % div]
|
||||||
|
routsum -= sir[0]
|
||||||
|
goutsum -= sir[1]
|
||||||
|
boutsum -= sir[2]
|
||||||
|
if (x == 0) {
|
||||||
|
vmin[y] = Math.min(y + r1, hm) * w
|
||||||
|
}
|
||||||
|
p = x + vmin[y]
|
||||||
|
sir[0] = r[p]
|
||||||
|
sir[1] = g[p]
|
||||||
|
sir[2] = b[p]
|
||||||
|
rinsum += sir[0]
|
||||||
|
ginsum += sir[1]
|
||||||
|
binsum += sir[2]
|
||||||
|
rsum += rinsum
|
||||||
|
gsum += ginsum
|
||||||
|
bsum += binsum
|
||||||
|
stackpointer = (stackpointer + 1) % div
|
||||||
|
sir = stack[stackpointer]
|
||||||
|
routsum += sir[0]
|
||||||
|
goutsum += sir[1]
|
||||||
|
boutsum += sir[2]
|
||||||
|
rinsum -= sir[0]
|
||||||
|
ginsum -= sir[1]
|
||||||
|
binsum -= sir[2]
|
||||||
|
yi += w
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
x++
|
||||||
|
}
|
||||||
|
bitmap.setPixels(pix, 0, w, 0, 0, w, h)
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
package app.myzel394.alibi.ui.utils
|
package app.myzel394.alibi.ui.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.hardware.display.DisplayManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Size
|
||||||
|
import android.view.Display
|
||||||
|
import android.view.WindowManager
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
@ -14,3 +21,28 @@ fun KeepScreenOn() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getScreenSize(context: Context): Size {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||||
|
val bounds = windowManager.currentWindowMetrics.bounds
|
||||||
|
|
||||||
|
Size(bounds.width(), bounds.height())
|
||||||
|
} else {
|
||||||
|
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
|
val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
|
||||||
|
|
||||||
|
val size = Point()
|
||||||
|
display.getRealSize(size)
|
||||||
|
|
||||||
|
Size(size.x, size.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberScreenSize(): Size {
|
||||||
|
val context = LocalView.current.context
|
||||||
|
|
||||||
|
return getScreenSize(context)
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.1.0' apply false
|
id 'com.android.application' version '8.1.1' apply false
|
||||||
id 'com.android.library' version '8.1.0' apply false
|
id 'com.android.library' version '8.1.1' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
|
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
|
||||||
|
|
||||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.21'
|
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.21'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user