[go: nahoru, domu]

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

[Sample] [Camera2] - Implementation of "UltraHDR Image Capture" #56

Merged
merged 8 commits into from
Jul 11, 2023
Next Next commit
[Sample] [Camera2] - Implementation of "Camera2 - UltraHDR Image Capt…
…ure"
  • Loading branch information
madebymozart committed Jul 8, 2023
commit a0e2f064ab88b92cfce78cdee111c4a9be7c9804
2 changes: 2 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Sample demonstrating how to make incoming call notifications and in call notific
This sample demonstrates how to capture and image and encode it into a JPEG
- [Camera2 - Preview](camera/camera2/src/main/java/com/example/platform/camera/preview/Camera2Preview.kt):
Demonstrates displaying processed pixel data directly from the camera sensor
- [Camera2 - UltraHDR Image Capture](camera/camera2/src/main/java/com/example/platform/camera/imagecapture/Camera2UltraHDRCapture.kt):
This sample demonstrates how to capture a 10-bit compressed still image and
- [Color Contrast](accessibility/src/main/java/com/example/platform/accessibility/ColorContrast.kt):
This sample demonstrates the importance of proper color contrast and how to
- [Connect to a GATT server](connectivity/bluetooth/ble/src/main/java/com/example/platform/connectivity/bluetooth/ble/ConnectGATTSample.kt):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,171 +18,102 @@ package com.example.platform.camera.common

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.graphics.Canvas
import android.graphics.ColorMatrixColorFilter
import android.graphics.Gainmap
import android.graphics.Paint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.ImageView
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.widget.ViewPager2
import coil.load
import com.example.platform.camera.databinding.FragmentImageViewerBinding
import com.example.platform.camera.databinding.AdvancedImageViewerBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.BufferedInputStream
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.math.max

class AdvancedImageViewer() : Fragment() {
/**
* Android ViewBinding.
*/
private var _binding: FragmentImageViewerBinding? = null
private val binding get() = _binding!!
class AdvancedImageViewer : Fragment() {
private enum class Type(val value: Int) {
JPEG(0),
JPEG_R(1);

/**
* Default Bitmap decoding options
*/
private val bitmapOptions = BitmapFactory.Options().apply {
inJustDecodeBounds = false
// Keep Bitmaps at less than 1 MP
if (max(outHeight, outWidth) > DOWN_SAMPLE_SIZE) {
val scaleFactorX = outWidth / DOWN_SAMPLE_SIZE + 1
val scaleFactorY = outHeight / DOWN_SAMPLE_SIZE + 1
inSampleSize = max(scaleFactorX, scaleFactorY)
companion object {
fun fromInt(value: Int) = Type.values().first { it.value == value }
}
}

/** Bitmap transformation derived from passed arguments */
private val bitmapTransformation: Matrix by lazy {
decodeExifOrientation(requireArguments().getInt(ARG_KEY_ORIENTATION))
}

/**
* Whether or not the resulting image has depth data or not.
*/
private val isDepth: Boolean by lazy {
requireArguments().getBoolean(ARG_KEY_IS_DEPTH)
}

/**
* Location of the image file to load.
*/
private val location: String by lazy {
requireArguments().getString(ARG_KEY_LOCATION, "")
}

/** Data backing our Bitmap viewpager */
private val bitmapList: MutableList<Bitmap> = mutableListOf()
/**
* [Bitmap] of the loaded image for.
*/

private fun imageViewFactory() = ImageView(requireContext()).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
}
/**
* Android ViewBinding.
*/
private var _binding: AdvancedImageViewerBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = FragmentImageViewerBinding.inflate(inflater, container, false)
binding.fragmentImageViewerViewpager2.apply {
// Populate the ViewPager and implement a cache of two media items
offscreenPageLimit = 2
adapter = GenericListAdapter(
bitmapList,
itemViewFactory = { imageViewFactory() },
) { view, item, _ ->
(view as ImageView).load(item) {
crossfade(true)
}
}
}

binding.fragmentImageViewerBack.setOnClickListener {
parentFragmentManager
.beginTransaction()
.remove(this)
.commit()
}

_binding = AdvancedImageViewerBinding.inflate(inflater, container, false)
return binding.root
}


override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch(Dispatchers.IO) {

val pager = binding.fragmentImageViewerViewpager2
// Load input image file
val inputBuffer = loadInputBuffer()

// Load the main JPEG image
addItemToViewPager(pager, decodeBitmap(inputBuffer, 0, inputBuffer.size))

// If we have depth data attached, attempt to load it
if (isDepth) {
try {
val depthStart = findNextJpegEndMarker(inputBuffer, 2)
addItemToViewPager(
pager,
decodeBitmap(
inputBuffer, depthStart, inputBuffer.size - depthStart,
),
)

val confidenceStart = findNextJpegEndMarker(inputBuffer, depthStart)
addItemToViewPager(
pager,
decodeBitmap(
inputBuffer, confidenceStart, inputBuffer.size - confidenceStart,
),
)

} catch (exc: RuntimeException) {
Log.e(TAG, "Invalid start marker for depth or confidence data")
}
}
}
}

/**
* Utility function used to read input file into a byte array.
*/
private fun loadInputBuffer(): ByteArray {
val inputFile = File(location)
return BufferedInputStream(inputFile.inputStream()).let { stream ->
ByteArray(stream.available()).also {
stream.read(it)
stream.close()
}
lifecycleScope.launch(Dispatchers.Main) {
val bitmap = getBitmapFromFile()
binding.imageContainer.setImageBitmap(bitmap)
}
}

/**
* Utility function used to add an item to the viewpager and notify it, in the main thread.
* Utility function to retrieve the bitmap representation of the file based on the [location].
*/
private fun addItemToViewPager(view: ViewPager2, item: Bitmap) = view.post {
bitmapList.add(item)
view.adapter?.notifyItemChanged(bitmapList.size - 1)
private suspend fun getBitmapFromFile(): Bitmap = withContext(Dispatchers.IO) {
val inputStream = File(location).inputStream()
BitmapFactory.decodeStream(inputStream)
}

/**
* Utility function used to decode a [Bitmap] from a byte array
* Creates a monochrome representation of the [Gainmap] of an UltraHDR image and returns it as
* a [Bitmap].
*/
private fun decodeBitmap(buffer: ByteArray, start: Int, length: Int): Bitmap {

// Load bitmap from given buffer
val bitmap = BitmapFactory.decodeByteArray(buffer, start, length, bitmapOptions)
@RequiresApi(34)
private fun visualizeGainmap(gainmap: Gainmap): Bitmap {
val contents = gainmap.gainmapContents
if (contents.config != Bitmap.Config.ALPHA_8) return contents

val visual = Bitmap.createBitmap(
contents.width, contents.height,
Bitmap.Config.ARGB_8888,
)

// Transform bitmap orientation using provided metadata
return Bitmap.createBitmap(
bitmap, 0, 0, bitmap.width, bitmap.height, bitmapTransformation, true,
val canvas = Canvas(visual)
val paint = Paint()
paint.colorFilter = ColorMatrixColorFilter(
floatArrayOf(
0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 0f, 255f,
),
)
canvas.drawBitmap(contents, 0f, 0f, paint)
canvas.setBitmap(null)
return visual
}

companion object {
Expand All @@ -191,38 +122,6 @@ class AdvancedImageViewer() : Fragment() {
/**
* Argument keys
*/
const val ARG_KEY_IS_DEPTH = "depth"
const val ARG_KEY_ORIENTATION = "orientation"
const val ARG_KEY_LOCATION = "location"

/** Maximum size of [Bitmap] decoded */
private const val DOWN_SAMPLE_SIZE: Int = 1024 // 1MP

/** These are the magic numbers used to separate the different JPG data chunks */
private val JPEG_DELIMITER_BYTES = arrayOf(-1, -39)

/**
* Utility function used to find the markers indicating separation between JPEG data chunks
*/
private fun findNextJpegEndMarker(jpegBuffer: ByteArray, start: Int): Int {

// Sanitize input arguments
assert(start >= 0) { "Invalid start marker: $start" }
assert(jpegBuffer.size > start) {
"Buffer size (${jpegBuffer.size}) smaller than start marker ($start)"
}

// Perform a linear search until the delimiter is found
for (i in start until jpegBuffer.size - 1) {
if (jpegBuffer[i].toInt() == JPEG_DELIMITER_BYTES[0] &&
jpegBuffer[i + 1].toInt() == JPEG_DELIMITER_BYTES[1]
) {
return i + 2
}
}

// If we reach this, it means that no marker was found
throw RuntimeException("Separator marker not found in buffer (${jpegBuffer.size})")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,9 @@ import android.view.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.core.os.bundleOf
import androidx.exifinterface.media.ExifInterface
import androidx.fragment.app.Fragment
import androidx.fragment.app.add
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import com.example.platform.camera.R
import com.example.platform.camera.common.*
import com.example.platform.camera.databinding.FragmentCamera2ImageCaptureBinding
import com.google.android.catalog.framework.annotations.Sample
Expand Down
Loading