diff --git a/app/build.gradle b/app/build.gradle
index bbdd34d..cca0dce 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,7 +8,7 @@ android {
defaultConfig {
applicationId "com.xypower.mppreview"
- minSdk 28
+ minSdk 30
targetSdk 30
versionCode 1
versionName "1.0"
@@ -18,7 +18,7 @@ android {
externalNativeBuild {
cmake {
// cppFlags '-std=c++17 -frtti -fexceptions -Wno-error=format-security'
- cppFlags '-std=c++17 -fexceptions -Wno-error=format-security -fopenmp'
+ cppFlags '-std=c++17 -fexceptions -Wno-error=format-security -fopenmp '
// cppFlags '-std=c++17 -Wno-error=format-security'
// arguments "-DANDROID_STL=c++_shared"
// arguments "-DNCNN_DISABLE_EXCEPTION=OFF", "-DOpenCV_DIR=" + opencvsdk + "/sdk/native/jni", "-DNCNN_ROOT=" + ncnnroot
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3e65198..9372d41 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,7 +5,8 @@
-
+
+
@@ -18,7 +19,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MpPreview"
- tools:targetApi="31">
+ android:requestLegacyExternalStorage="true"
+ tools:targetApi="30">
// #include "ncnn/yolov5ncnn.h"
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
#include
#include
#include
+#define HDR_TAG "HDR"
+#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, HDR_TAG,__VA_ARGS__)
+#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, HDR_TAG,__VA_ARGS__)
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, HDR_TAG, __VA_ARGS__)
+#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, HDR_TAG, __VA_ARGS__)
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, HDR_TAG,__VA_ARGS__)
+
+bool AndroidBitmap_CompressWriteFile(void *userContext, const void *data, size_t size)
+{
+ int file = (int)((size_t)userContext);
+ int bytesWritten = write(file, data, size);
+ return bytesWritten == size;
+}
+
+bool AndroidBitmap_CompressWriteBuffer(void *userContext, const void *data, size_t size)
+{
+ std::vector* buffer = (std::vector*)userContext;
+ // int bytesWritten = write(file, data, size);
+ const uint8_t* pBytes = (const uint8_t*)data;
+ buffer->insert(buffer->cend(), pBytes, pBytes + size);
+ return true;
+}
+
+void ConvertDngToPng(const uint8_t* buffer, size_t bufferLength, std::vector& pngData)
+{
+ AImageDecoder* imageDecoder = NULL;
+ AImageDecoder_createFromBuffer(buffer, bufferLength, &imageDecoder);
+
+ // int fd = open("/sdcard/com.xypower.mpapp/tmp/4.dng", O_RDONLY);
+ // AImageDecoder_createFromFd(fd, &imageDecoder);
+
+ const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(imageDecoder);
+ const char *mimeType = AImageDecoderHeaderInfo_getMimeType(headerInfo);
+ AndroidBitmapInfo bmpInfo = { 0 };
+ bmpInfo.flags = AImageDecoderHeaderInfo_getAlphaFlags(headerInfo);
+ bmpInfo.width = AImageDecoderHeaderInfo_getWidth(headerInfo);
+ bmpInfo.height = AImageDecoderHeaderInfo_getHeight(headerInfo);
+ bmpInfo.format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(headerInfo);
+ bmpInfo.stride = AImageDecoder_getMinimumStride(imageDecoder); // Image decoder does not
+ // use padding by default
+ int32_t fmt = ANDROID_BITMAP_FORMAT_RGBA_8888;
+ size_t stride = bmpInfo.width * 4;
+ size_t size = stride * bmpInfo.height;
+ size = bmpInfo.stride * bmpInfo.height;
+
+ int32_t dataSpace = AImageDecoderHeaderInfo_getDataSpace(headerInfo);
+
+ std::vector frame;
+ frame.resize(size);
+
+ // AImageDecoder_setTargetSize(imageDecoder, 5376, 3024);
+ int result = AImageDecoder_decodeImage(imageDecoder, (void *)(&frame[0]), bmpInfo.stride, size);
+
+ // close(fd);
+
+ if (result == ANDROID_IMAGE_DECODER_SUCCESS)
+ {
+ // std::string imagePath = "/data/data/com.xypower.mppreview/files/test.png";
+ // int file = open(imagePath.c_str(), O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);
+ // if (file == -1) {}
+ pngData.clear();
+ AndroidBitmap_compress(&bmpInfo, dataSpace, &frame[0], ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, (void*)&pngData, AndroidBitmap_CompressWriteBuffer);
+ // close(file);
+ std::vector empty;
+ empty.swap(frame);
+ }
+
+ AImageDecoder_delete(imageDecoder);
+}
+
+void ConvertDngToPng(const uint8_t* buffer, size_t bufferLength, cv::Mat& rgb)
+{
+ AImageDecoder* imageDecoder = NULL;
+ AImageDecoder_createFromBuffer(buffer, bufferLength, &imageDecoder);
+
+ // int fd = open("/sdcard/com.xypower.mpapp/tmp/4.dng", O_RDONLY);
+ // AImageDecoder_createFromFd(fd, &imageDecoder);
+
+ const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(imageDecoder);
+ const char *mimeType = AImageDecoderHeaderInfo_getMimeType(headerInfo);
+ AndroidBitmapInfo bmpInfo = { 0 };
+ bmpInfo.flags = AImageDecoderHeaderInfo_getAlphaFlags(headerInfo);
+ bmpInfo.width = AImageDecoderHeaderInfo_getWidth(headerInfo);
+ bmpInfo.height = AImageDecoderHeaderInfo_getHeight(headerInfo);
+ bmpInfo.format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(headerInfo);
+ bmpInfo.stride = AImageDecoder_getMinimumStride(imageDecoder); // Image decoder does not
+ // use padding by default
+ int32_t fmt = ANDROID_BITMAP_FORMAT_RGBA_8888;
+ size_t stride = bmpInfo.width * 4;
+ size_t size = stride * bmpInfo.height;
+ size = bmpInfo.stride * bmpInfo.height;
+
+ int32_t dataSpace = AImageDecoderHeaderInfo_getDataSpace(headerInfo);
+
+ std::vector frame;
+ frame.resize(size);
+
+ // AImageDecoder_setTargetSize(imageDecoder, 5376, 3024);
+ int result = AImageDecoder_decodeImage(imageDecoder, (void *)(&frame[0]), bmpInfo.stride, size);
+
+ // close(fd);
+
+ if (result == ANDROID_IMAGE_DECODER_SUCCESS)
+ {
+ cv::Mat tmp(bmpInfo.height, bmpInfo.width, CV_8UC4, &frame[0]);
+ tmp.copyTo(rgb);
+
+ //convert RGB to BGR
+ cv::cvtColor(rgb, rgb, cv::COLOR_RGB2BGR);
+
+ /*
+ // std::string imagePath = "/data/data/com.xypower.mppreview/files/test.png";
+ // int file = open(imagePath.c_str(), O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);
+ // if (file == -1) {}
+ std::vector pngData;
+ result = AndroidBitmap_compress(&bmpInfo, dataSpace, &frame[0], ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, (void*)&pngData, AndroidBitmap_CompressWriteBuffer);
+ {
+ std::vector empty;
+ empty.swap(frame);
+ }
+ // close(file);
+ if (ANDROID_BITMAP_RESULT_SUCCESS == result)
+ {
+ rgb = cv::imdecode(pngData, cv::IMREAD_COLOR);
+ }
+ */
+ }
+
+ AImageDecoder_delete(imageDecoder);
+}
+
inline std::string jstring2string(JNIEnv *env, jstring jStr)
{
@@ -28,11 +168,12 @@ bool makeHdr(std::vector& times, std::vector& paths, cv::Mat
{
// Read images and exposure times
std::vector images;
+ images.resize(paths.size());
- for (auto it = paths.cbegin(); it != paths.cend(); ++it)
+#pragma omp parallel for
+ for (int idx = 0; idx < paths.size(); idx++)
{
- cv::Mat im = cv::imread((*it).c_str());
- images.push_back(im);
+ images[idx] = cv::imread(paths[idx].c_str());
}
// Align input images
// cout << "Aligning images ... " << endl;
@@ -77,17 +218,66 @@ bool makeHdr(std::vector& times, std::vector& paths, cv::Mat
return true;
}
+
+bool makeHdr(std::vector& times, std::vector& images, cv::Mat& rgb)
+{
+ // Read images and exposure times
+
+ // Align input images
+ // cout << "Aligning images ... " << endl;
+ cv::Ptr alignMTB = cv::createAlignMTB();
+#if 0
+ alignMTB->process(images, images);
+#endif
+
+ // Obtain Camera Response Function (CRF)
+ // cout << "Calculating Camera Response Function (CRF) ... " << endl;
+ cv::Mat responseDebevec;
+ cv::Ptr calibrateDebevec = cv::createCalibrateDebevec();
+ calibrateDebevec->process(images, responseDebevec, times);
+
+ // Merge images into an HDR linear image
+ // cout << "Merging images into one HDR image ... ";
+ cv::Mat hdrDebevec;
+ cv::Ptr mergeDebevec = cv::createMergeDebevec();
+ mergeDebevec->process(images, hdrDebevec, times, responseDebevec);
+ // Save HDR image.
+ // imwrite((OUTPUT_DIR "hdrDebevec.hdr"), hdrDebevec);
+ // cout << "saved hdrDebevec.hdr " << endl;
+
+ {
+ std::vector empty;
+ empty.swap(images);
+ }
+
+ // Tonemap using Reinhard's method to obtain 24-bit color image
+ // cout << "Tonemaping using Reinhard's method ... ";
+ cv::Mat ldrReinhard;
+ cv::Ptr tonemapReinhard = cv::createTonemapReinhard(1.5, 0, 0, 0);
+ tonemapReinhard->process(hdrDebevec, ldrReinhard);
+ hdrDebevec.release();
+
+ int type = ldrReinhard.type();
+ ldrReinhard = ldrReinhard * 255;
+
+ ldrReinhard.convertTo(rgb, CV_8U);
+ ldrReinhard.release();
+
+ return true;
+}
+
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_xypower_mppreview_Camera2RawFragment_makeHdr(
JNIEnv *env, jobject thiz, jlong exposureTime1, jstring path1, jlong exposureTime2,
jstring path2, jstring outputPath) {
+ cv::setNumThreads(4);
std::vector times;
std::vector paths;
- times.push_back((float)(exposureTime1 / 1000) / 1000000.0);
- times.push_back((float)(exposureTime2 / 1000) / 1000000.0);
+ times.push_back((double)(exposureTime1) / 1000000000.0);
+ times.push_back((double)(exposureTime2) / 1000000000.0);
paths.push_back(jstring2string(env, path1));
paths.push_back(jstring2string(env, path2));
@@ -111,6 +301,7 @@ Java_com_xypower_mppreview_Camera2RawFragment_makeHdr2(
JNIEnv *env, jobject thiz, jlong exposureTime1, jstring path1, jlong exposureTime2,
jstring path2, jlong exposureTime3, jstring path3, jstring outputPath) {
+ cv::setNumThreads(4);
std::vector times;
std::vector paths;
@@ -188,4 +379,58 @@ Java_com_xypower_mppreview_Camera2RawFragment_makeHdr2(
// /*Mat fusion;
// Ptr merge_mertens = createMergeMertens();
// merge_mertens->process(images, *(Mat *)hdrImg);*/
-//}
\ No newline at end of file
+//}
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_xypower_mppreview_MainActivity_test(JNIEnv *env, jobject thiz) {
+ // TODO: implement test()
+}
+extern "C"
+JNIEXPORT jboolean JNICALL
+Java_com_xypower_mppreview_Camera2RawFragment_makeHdr3(JNIEnv *env, jclass clazz,
+ jlong exposureTime1, jobject img1, jint length1,
+ jlong exposureTime2, jobject img2, jint length2,
+ jstring outputPath) {
+
+ ALOGI("Start HDR3");
+
+ std::vector images;
+ images.resize(2);
+
+ std::vector pngLengths;
+ pngLengths.push_back(length1);
+ pngLengths.push_back(length2);
+ std::vector pngDatas;
+ pngDatas.resize(2);
+ pngDatas[0] = (const uint8_t*)env->GetDirectBufferAddress(img1);
+ pngDatas[1] = (const uint8_t*)env->GetDirectBufferAddress(img2);
+
+ // omp_set_num_threads(2);
+#pragma omp parallel for num_threads(2)
+ for (int idx = 0; idx < 2; idx++)
+ {
+ ConvertDngToPng(pngDatas[idx], pngLengths[idx], images[idx]);
+ }
+
+ cv::Mat rgb;
+
+ std::vector times;
+ times.push_back((double)(exposureTime1) / 1000000000.0);
+ times.push_back((double)(exposureTime2) / 1000000000.0);
+
+ ALOGI("Start MakeHDR3");
+ makeHdr(times, images, rgb);
+ ALOGI("End MakeHDR3");
+
+ std::string fileName = jstring2string(env, outputPath);
+ std::vector params;
+ params.push_back(cv::IMWRITE_JPEG_QUALITY);
+ params.push_back(100);
+ if (cv::imwrite(fileName.c_str(), rgb, params))
+ {
+ ALOGI("End HDR3");
+ return JNI_TRUE;
+ }
+
+ return JNI_FALSE;
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/xypower/mppreview/ByteBufferOutputStream.java b/app/src/main/java/com/xypower/mppreview/ByteBufferOutputStream.java
new file mode 100644
index 0000000..69ece76
--- /dev/null
+++ b/app/src/main/java/com/xypower/mppreview/ByteBufferOutputStream.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.xypower.mppreview;
+
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * A ByteBuffer-backed OutputStream that expands the internal ByteBuffer as required. Given this, the caller should
+ * always access the underlying ByteBuffer via the {@link #buffer()} method until all writes are completed.
+ *
+ * This class is typically used for 2 purposes:
+ *
+ * 1. Write to a ByteBuffer when there is a chance that we may need to expand it in order to fit all the desired data
+ * 2. Write to a ByteBuffer via methods that expect an OutputStream interface
+ *
+ * Hard to track bugs can happen when this class is used for the second reason and unexpected buffer expansion happens.
+ * So, it's best to assume that buffer expansion can always happen. An improvement would be to create a separate class
+ * that throws an error if buffer expansion is required to avoid the issue altogether.
+ */
+public class ByteBufferOutputStream extends OutputStream {
+
+ private static final float REALLOCATION_FACTOR = 1.1f;
+
+ private final int initialCapacity;
+ private final int initialPosition;
+ private ByteBuffer buffer;
+
+ /**
+ * Creates an instance of this class that will write to the received `buffer` up to its `limit`. If necessary to
+ * satisfy `write` or `position` calls, larger buffers will be allocated so the {@link #buffer()} method may return
+ * a different buffer than the received `buffer` parameter.
+ *
+ * Prefer one of the constructors that allocate the internal buffer for clearer semantics.
+ */
+ public ByteBufferOutputStream(ByteBuffer buffer) {
+ this.buffer = buffer;
+ this.initialPosition = buffer.position();
+ this.initialCapacity = buffer.capacity();
+ }
+
+ public ByteBufferOutputStream(int initialCapacity) {
+ this(initialCapacity, false);
+ }
+
+ public ByteBufferOutputStream(int initialCapacity, boolean directBuffer) {
+ this(directBuffer ? ByteBuffer.allocateDirect(initialCapacity) : ByteBuffer.allocate(initialCapacity));
+ }
+
+ public void write(int b) {
+ ensureRemaining(1);
+ buffer.put((byte) b);
+ }
+
+ public void write(byte[] bytes, int off, int len) {
+ ensureRemaining(len);
+ buffer.put(bytes, off, len);
+ }
+
+ public void write(ByteBuffer sourceBuffer) {
+ ensureRemaining(sourceBuffer.remaining());
+ buffer.put(sourceBuffer);
+ }
+
+ public ByteBuffer buffer() {
+ return buffer;
+ }
+
+ public int position() {
+ return buffer.position();
+ }
+
+ public int remaining() {
+ return buffer.remaining();
+ }
+
+ public int limit() {
+ return buffer.limit();
+ }
+
+ public void position(int position) {
+ ensureRemaining(position - buffer.position());
+ buffer.position(position);
+ }
+
+ /**
+ * The capacity of the first internal ByteBuffer used by this class. This is useful in cases where a pooled
+ * ByteBuffer was passed via the constructor and it needs to be returned to the pool.
+ */
+ public int initialCapacity() {
+ return initialCapacity;
+ }
+
+ /**
+ * Ensure there is enough space to write some number of bytes, expanding the underlying buffer if necessary.
+ * This can be used to avoid incremental expansions through calls to {@link #write(int)} when you know how
+ * many total bytes are needed.
+ *
+ * @param remainingBytesRequired The number of bytes required
+ */
+ public void ensureRemaining(int remainingBytesRequired) {
+ if (remainingBytesRequired > buffer.remaining())
+ expandBuffer(remainingBytesRequired);
+ }
+
+ private void expandBuffer(int remainingRequired) {
+ int expandSize = Math.max((int) (buffer.limit() * REALLOCATION_FACTOR), buffer.position() + remainingRequired);
+ ByteBuffer temp = ByteBuffer.allocate(expandSize);
+ int limit = limit();
+ buffer.flip();
+ temp.put(buffer);
+ buffer.limit(limit);
+ // reset the old buffer's position so that the partial data in the new buffer cannot be mistakenly consumed
+ // we should ideally only do this for the original buffer, but the additional complexity doesn't seem worth it
+ buffer.position(initialPosition);
+ buffer = temp;
+ }
+
+}
diff --git a/app/src/main/java/com/xypower/mppreview/Camera2RawFragment.java b/app/src/main/java/com/xypower/mppreview/Camera2RawFragment.java
index 418919f..2ba64bb 100644
--- a/app/src/main/java/com/xypower/mppreview/Camera2RawFragment.java
+++ b/app/src/main/java/com/xypower/mppreview/Camera2RawFragment.java
@@ -64,6 +64,7 @@ import com.xypower.mppreview.widget.ErrorDialog;
import java.io.File;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -138,6 +139,7 @@ public class Camera2RawFragment extends Fragment {
public static native boolean makeHdr2(long exposureTime1, String path1, long exposureTime2, String path2, long exposureTime3, String path3, String outputPath);
+ public static native boolean makeHdr3(long exposureTime1, ByteBuffer img1, int length1, long exposureTime2, ByteBuffer img2, int length2, String outputPath);
private int mExposureComp = MainActivity.ExposureComp;
@@ -1102,6 +1104,31 @@ public class Camera2RawFragment extends Fragment {
ArrayList mlist = new ArrayList<>();
List requests = new ArrayList<>();
double v = 0;
+
+ ImageSaver.ImagePair imagePair = new ImageSaver.ImagePair(2);
+ ImageSaver.ImagePairRunnable runnable = new ImageSaver.ImagePairRunnable(imagePair) {
+ @Override
+ public void run() {
+ final List images = imagePair.getImages();
+ final String outputPath = "/sdcard/DCIM/";
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // makeHdr3()
+
+ if (images.size() != 2) {
+ return;
+ }
+ ImageSaver.ImageInfo img1 = images.get(0);
+ ImageSaver.ImageInfo img2 = images.get(1);
+ makeHdr3(img1.exposureTime, img1.byteBuffer, img1.length, img2.exposureTime, img2.byteBuffer, img2.length, outputPath + "HDR_" + HdrUtil.generateTimestamp() + ".jpg");
+ }
+ }).start();
+ }
+ };
+
+ imagePair.setRunnable(runnable);
+
for (int idx = 0; idx < 2; idx++) {
// Set request tag to easily track results in callbacks.
captureBuilder.setTag(mRequestCounter.getAndIncrement());
@@ -1140,6 +1167,7 @@ public class Camera2RawFragment extends Fragment {
// ImageSaverBuilder jpegBuilder = new ImageSaverBuilder(activity).setCharacteristics(mCharacteristics);
ImageSaverBuilder rawBuilder = new ImageSaverBuilder(activity).setCharacteristics(mCharacteristics);//保存拍照参数
+ rawBuilder.setImagePair(imagePair);
rawBuilder.setCallback(new CompleteCallback() {
@Override
public void onResult() {
diff --git a/app/src/main/java/com/xypower/mppreview/HdrUtil.java b/app/src/main/java/com/xypower/mppreview/HdrUtil.java
index 90a1811..f769687 100644
--- a/app/src/main/java/com/xypower/mppreview/HdrUtil.java
+++ b/app/src/main/java/com/xypower/mppreview/HdrUtil.java
@@ -9,7 +9,7 @@ import java.util.Locale;
public class HdrUtil {
public static String generateTimestamp() {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", Locale.US);
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US);
return sdf.format(new Date());
}
diff --git a/app/src/main/java/com/xypower/mppreview/ImageSaver.java b/app/src/main/java/com/xypower/mppreview/ImageSaver.java
index c89a885..3913f45 100644
--- a/app/src/main/java/com/xypower/mppreview/ImageSaver.java
+++ b/app/src/main/java/com/xypower/mppreview/ImageSaver.java
@@ -18,27 +18,88 @@ import android.widget.Toast;
import com.xypower.mppreview.bean.Contants;
import com.xypower.mppreview.bean.PngPhotoBean;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.List;
public class ImageSaver implements Runnable {
+ private final static String TAG = "HDR";
private final Image mImage;
private final File mFile;
private final CaptureResult mCaptureResult;
private final CameraCharacteristics mCharacteristics;
private CompleteCallback mCallback;
private final Context mContext;
+ private final ImagePair mImagePair;
private final Camera2RawFragment.RefCountedAutoCloseable mReader;
+ public static class ImageInfo {
+ public long exposureTime;
+ public int length;
+ public ByteBuffer byteBuffer;
+
+ ImageInfo(ByteBuffer bb, int length, long exp) {
+ this.exposureTime = exp;
+ this.length = length;
+ byteBuffer = bb;
+ }
+ }
+
+ public static class ImagePair {
+ private List mImages;
+ private int mExpectedCount;
+ private Runnable mRunnable;
+
+ public ImagePair(int expectedCount) {
+ mImages = new ArrayList<>();
+ mExpectedCount = expectedCount;
+ mRunnable = null;
+ }
+
+ public void setRunnable(Runnable runnable) {
+ mRunnable = runnable;
+ }
+
+ public void addImage(ByteBuffer byteBuffer, int length, long exp) {
+ boolean isFull = false;
+ ImageInfo imageInfo = new ImageInfo(byteBuffer, length, exp);
+ synchronized (mImages) {
+ mImages.add(imageInfo);
+ isFull = (mImages.size() == mExpectedCount);
+ }
+
+ if (mRunnable != null && isFull) {
+ mRunnable.run();
+ }
+ }
+
+ public List getImages() {
+ return mImages;
+ }
+ }
+
+ public static abstract class ImagePairRunnable implements Runnable {
+
+ protected ImagePair mImagePair;
+
+ public ImagePairRunnable(ImagePair imagePair) {
+ mImagePair = imagePair;
+ }
+
+ }
+
private ArrayList mlist = new ArrayList<>();//用来存储已拍照的照片名称
- public ImageSaver(Image image, File file, CaptureResult result, CameraCharacteristics characteristics, Context context, Camera2RawFragment.RefCountedAutoCloseable reader, ArrayList list,CompleteCallback callback) {
+ public ImageSaver(Image image, File file, CaptureResult result, CameraCharacteristics characteristics, Context context,
+ Camera2RawFragment.RefCountedAutoCloseable reader, ArrayList list,
+ CompleteCallback callback, ImagePair imagePair) {
mImage = image;
mFile = file;
mCaptureResult = result;
@@ -47,6 +108,7 @@ public class ImageSaver implements Runnable {
mReader = reader;
mlist = list;
mCallback = callback;
+ mImagePair = imagePair;
}
@Override
@@ -73,16 +135,26 @@ public class ImageSaver implements Runnable {
}
case ImageFormat.RAW_SENSOR: {
DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult);
- FileOutputStream output = null;
+
+ ByteBuffer byteBuffer = null;
+ ByteBufferOutputStream baos = null;
+
+ Long t = mCaptureResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
+
try {
- output = new FileOutputStream(mFile);
- dngCreator.writeImage(output, mImage);
+ byteBuffer = ByteBuffer.allocateDirect(mImage.getWidth() * mImage.getHeight() * 2 + 81768);
+ baos = new ByteBufferOutputStream(byteBuffer);
+ Log.d(TAG, "Before Saving DNG");
+ dngCreator.writeImage(baos, mImage);
+ Log.d(TAG, "After Saving DNG pos=" + byteBuffer.position());
+
+ mImagePair.addImage(byteBuffer, byteBuffer.position(), t.longValue());
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
mImage.close();
- closeOutput(output);
+ closeOutput(baos);
}
break;
}
@@ -91,49 +163,6 @@ public class ImageSaver implements Runnable {
}
}
mReader.close();
- if (success) {
- Long t = mCaptureResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
- File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
- String directoryPath = directory.getPath();
- File file = new File(directory, "create_" + t + "_" + generateTimestamp() + ".png");
- String path = file.getPath();
- try {
- Log.e("测试测试", "开始转换");
- RawToJpgConverter.convertRawToJpg(mFile.getPath(), path);
- Log.e("测试测试", "转换结束");
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- PngPhotoBean bean = new PngPhotoBean();
- bean.setEtime(t);
- bean.setPath(path);
- mlist.add(bean);
- if (mFile.exists()) {
- mFile.delete();
- }
- if (mlist.size() == 2) {
- PngPhotoBean bean1 = mlist.get(0);
- PngPhotoBean bean2 = mlist.get(1);
- String path1 = bean1.getPath();
- String path2 = bean2.getPath();
- String outputPath = directoryPath + "/" + "hdr_" + generateTimestamp() + ".png";
- Log.e("测试测试", "开始合成");
- boolean b = makeHdr(bean1.getEtime(), path1, bean2.getEtime(), path2, outputPath);
- Log.e("测试测试", "合成结束");
- if (b) {
- mCallback.onResult();
-// Toast.makeText(mContext.getApplicationContext(), "图片HDR成功", Toast.LENGTH_SHORT).show();
- File file1 = new File(path1);
- File file2 = new File(path2);
- if (file1.exists()) {
- file1.delete();
- }
- if (file2.exists()) {
- file2.delete();
- }
- }
- }
- }
}
private static void closeOutput(OutputStream outputStream) {
diff --git a/app/src/main/java/com/xypower/mppreview/ImageSaverBuilder.java b/app/src/main/java/com/xypower/mppreview/ImageSaverBuilder.java
index 618350d..bc27c26 100644
--- a/app/src/main/java/com/xypower/mppreview/ImageSaverBuilder.java
+++ b/app/src/main/java/com/xypower/mppreview/ImageSaverBuilder.java
@@ -19,6 +19,7 @@ public class ImageSaverBuilder {
public CameraCharacteristics mCharacteristics;
public Context mContext;
public Camera2RawFragment.RefCountedAutoCloseable mReader;
+ public ImageSaver.ImagePair mImagePair;
private ArrayList mlist;
private CompleteCallback mCallback;
@@ -46,6 +47,12 @@ public class ImageSaverBuilder {
return this;
}
+ public synchronized ImageSaverBuilder setImagePair(final ImageSaver.ImagePair imagePair) {
+ if (imagePair == null) throw new NullPointerException();
+ mImagePair = imagePair;
+ return this;
+ }
+
public synchronized ImageSaverBuilder setFile(final File file) {
if (file == null) throw new NullPointerException();
mFile = file;
@@ -78,7 +85,7 @@ public class ImageSaverBuilder {
if (!isComplete()) {
return null;
}
- return new ImageSaver(mImage, mFile, mCaptureResult, mCharacteristics, mContext, mReader, mlist,mCallback);
+ return new ImageSaver(mImage, mFile, mCaptureResult, mCharacteristics, mContext, mReader, mlist,mCallback, mImagePair);
}
public synchronized String getSaveLocation() {
diff --git a/app/src/main/java/com/xypower/mppreview/MainActivity.java b/app/src/main/java/com/xypower/mppreview/MainActivity.java
index 828530b..25bf06c 100644
--- a/app/src/main/java/com/xypower/mppreview/MainActivity.java
+++ b/app/src/main/java/com/xypower/mppreview/MainActivity.java
@@ -1,12 +1,17 @@
package com.xypower.mppreview;
+import static java.lang.System.loadLibrary;
+
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import android.Manifest;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.Editable;
import android.view.View;
@@ -17,8 +22,16 @@ import android.widget.Spinner;
import com.xypower.mppreview.bean.Contants;
+import java.io.File;
+
public class MainActivity extends AppCompatActivity implements View.OnClickListener, AdapterView.OnItemSelectedListener {
+ static {
+ loadLibrary("mppreview");
+ }
+
+ private static int MY_PERMISSIONS_REQUEST_FOREGROUND_SERVICE = 100;
+
public static int ExposureComp = 0;
private Button systakepic;
private Button hdrtakepic;
@@ -26,12 +39,46 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
private int picsize = 0;
+ protected native void test();
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initActivityResult();
+
+
+ String[] accessPermissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.FOREGROUND_SERVICE, Manifest.permission.READ_PHONE_STATE,
+ /*Manifest.permission.PACKAGE_USAGE_STATS,*/
+ /*Manifest.permission.SET_TIME,*/};
+ boolean needRequire = false;
+ for (String access : accessPermissions) {
+ int curPermission = ActivityCompat.checkSelfPermission(MainActivity.this, access);
+ if (curPermission != PackageManager.PERMISSION_GRANTED) {
+ needRequire = true;
+ break;
+ }
+ }
+ if (needRequire) {
+ ActivityCompat.requestPermissions(MainActivity.this, accessPermissions, MY_PERMISSIONS_REQUEST_FOREGROUND_SERVICE);
+ // return;
+ }
+
+ File file = this.getFilesDir();
+
+ String path = "/sdcard/com.xypower.mppreview/";
+ file = new File(path);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ test();
+ }
+ }).start();
}
private void initView() {