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() {