|
|
@ -12,6 +12,7 @@ import android.graphics.Point;
|
|
|
|
import android.graphics.Rect;
|
|
|
|
import android.graphics.Rect;
|
|
|
|
import android.graphics.RectF;
|
|
|
|
import android.graphics.RectF;
|
|
|
|
import android.graphics.SurfaceTexture;
|
|
|
|
import android.graphics.SurfaceTexture;
|
|
|
|
|
|
|
|
import android.graphics.YuvImage;
|
|
|
|
import android.hardware.SensorManager;
|
|
|
|
import android.hardware.SensorManager;
|
|
|
|
import android.hardware.camera2.CameraAccessException;
|
|
|
|
import android.hardware.camera2.CameraAccessException;
|
|
|
|
import android.hardware.camera2.CameraCaptureSession;
|
|
|
|
import android.hardware.camera2.CameraCaptureSession;
|
|
|
@ -259,7 +260,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
* captures. This is used to allow us to clean up the {@link ImageReader} when all background
|
|
|
|
* captures. This is used to allow us to clean up the {@link ImageReader} when all background
|
|
|
|
* tasks using its {@link Image}s have completed.
|
|
|
|
* tasks using its {@link Image}s have completed.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
// private RefCountedAutoCloseable<ImageReader> mJpegImageReader;
|
|
|
|
private RefCountedAutoCloseable<ImageReader> mJpegImageReader;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* A reference counted holder wrapping the {@link ImageReader} that handles RAW image captures.
|
|
|
|
* A reference counted holder wrapping the {@link ImageReader} that handles RAW image captures.
|
|
|
@ -365,7 +366,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public void onImageAvailable(ImageReader reader) {
|
|
|
|
public void onImageAvailable(ImageReader reader) {
|
|
|
|
// dequeueAndSaveImage(mJpegResultQueue, mJpegImageReader);
|
|
|
|
dequeueAndSaveImage(mJpegResultQueue, mJpegImageReader);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
@ -378,7 +379,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public void onImageAvailable(ImageReader reader) {
|
|
|
|
public void onImageAvailable(ImageReader reader) {
|
|
|
|
dequeueAndSaveImage(mRawResultQueue, mRawImageReader);
|
|
|
|
// dequeueAndSaveImage(mRawResultQueue, mRawImageReader);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
@ -386,7 +387,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
private Long exposetime;
|
|
|
|
private Long exposetime;
|
|
|
|
private Integer sensitivity;
|
|
|
|
private Integer sensitivity;
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* /**
|
|
|
|
* A {@link CameraCaptureSession.CaptureCallback} that handles events for the preview and
|
|
|
|
* A {@link CameraCaptureSession.CaptureCallback} that handles events for the preview and
|
|
|
|
* pre-capture sequence.
|
|
|
|
* pre-capture sequence.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
@ -472,7 +473,8 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
String currentDateTime = generateTimestamp();
|
|
|
|
String currentDateTime = generateTimestamp();
|
|
|
|
File rawFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "RAW_" + currentDateTime + ".dng");
|
|
|
|
File rawFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "RAW_" + currentDateTime + ".dng");
|
|
|
|
// File rawFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "RAW_a.dng");
|
|
|
|
// File rawFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "RAW_a.dng");
|
|
|
|
File jpegFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "JPEG_" + currentDateTime + ".jpg");
|
|
|
|
// File jpegFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "JPEG_" + currentDateTime + ".jpg");
|
|
|
|
|
|
|
|
File jpegFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "YUV_" + currentDateTime + ".bmp");
|
|
|
|
|
|
|
|
|
|
|
|
// Look up the ImageSaverBuilder for this request and update it with the file name
|
|
|
|
// Look up the ImageSaverBuilder for this request and update it with the file name
|
|
|
|
// based on the capture start time.
|
|
|
|
// based on the capture start time.
|
|
|
@ -654,13 +656,9 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public void onResume() {
|
|
|
|
public void onResume() {
|
|
|
|
super.onResume();
|
|
|
|
super.onResume();
|
|
|
|
startBackgroundThread();
|
|
|
|
startBackgroundThread();//开启一个后台线程
|
|
|
|
openCamera();
|
|
|
|
openCamera();
|
|
|
|
|
|
|
|
|
|
|
|
// When the screen is turned off and turned back on, the SurfaceTexture is already
|
|
|
|
|
|
|
|
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we should
|
|
|
|
|
|
|
|
// configure the preview bounds here (otherwise, we wait until the surface is ready in
|
|
|
|
|
|
|
|
// the SurfaceTextureListener).
|
|
|
|
|
|
|
|
if (mTextureView.isAvailable()) {
|
|
|
|
if (mTextureView.isAvailable()) {
|
|
|
|
configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
|
|
|
|
configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
@ -771,7 +769,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
|
|
|
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
|
|
|
|
|
|
|
|
|
|
|
// For still image captures, we use the largest available size.
|
|
|
|
// For still image captures, we use the largest available size.
|
|
|
|
Size largestJpeg = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea());
|
|
|
|
Size largestJpeg = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)), new CompareSizesByArea());
|
|
|
|
|
|
|
|
|
|
|
|
Size[] rawSizes = map.getOutputSizes(ImageFormat.RAW_SENSOR);
|
|
|
|
Size[] rawSizes = map.getOutputSizes(ImageFormat.RAW_SENSOR);
|
|
|
|
Size largestRaw = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.RAW_SENSOR)), new CompareSizesByArea());
|
|
|
|
Size largestRaw = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.RAW_SENSOR)), new CompareSizesByArea());
|
|
|
@ -781,10 +779,10 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
// Set up ImageReaders for JPEG and RAW outputs. Place these in a reference
|
|
|
|
// Set up ImageReaders for JPEG and RAW outputs. Place these in a reference
|
|
|
|
// counted wrapper to ensure they are only closed when all background tasks
|
|
|
|
// counted wrapper to ensure they are only closed when all background tasks
|
|
|
|
// using them are finished.
|
|
|
|
// using them are finished.
|
|
|
|
// if (mJpegImageReader == null || mJpegImageReader.getAndRetain() == null) {
|
|
|
|
if (mJpegImageReader == null || mJpegImageReader.getAndRetain() == null) {
|
|
|
|
// mJpegImageReader = new RefCountedAutoCloseable<>(ImageReader.newInstance(largestJpeg.getWidth(), largestJpeg.getHeight(), ImageFormat.JPEG, /*maxImages*/5));
|
|
|
|
mJpegImageReader = new RefCountedAutoCloseable<>(ImageReader.newInstance(largestJpeg.getWidth(), largestJpeg.getHeight(), ImageFormat.YUV_420_888, /*maxImages*/5));
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
// mJpegImageReader.get().setOnImageAvailableListener(mOnJpegImageAvailableListener, mBackgroundHandler);
|
|
|
|
mJpegImageReader.get().setOnImageAvailableListener(mOnJpegImageAvailableListener, mBackgroundHandler);
|
|
|
|
|
|
|
|
|
|
|
|
if (mRawImageReader == null || mRawImageReader.getAndRetain() == null) {
|
|
|
|
if (mRawImageReader == null || mRawImageReader.getAndRetain() == null) {
|
|
|
|
mRawImageReader = new RefCountedAutoCloseable<>(ImageReader.newInstance(largestRaw.getWidth(), largestRaw.getHeight(), ImageFormat.RAW_SENSOR, /*maxImages*/ 5));
|
|
|
|
mRawImageReader = new RefCountedAutoCloseable<>(ImageReader.newInstance(largestRaw.getWidth(), largestRaw.getHeight(), ImageFormat.RAW_SENSOR, /*maxImages*/ 5));
|
|
|
@ -900,10 +898,10 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
mCameraDevice.close();
|
|
|
|
mCameraDevice.close();
|
|
|
|
mCameraDevice = null;
|
|
|
|
mCameraDevice = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if (null != mJpegImageReader) {
|
|
|
|
if (null != mJpegImageReader) {
|
|
|
|
// mJpegImageReader.close();
|
|
|
|
mJpegImageReader.close();
|
|
|
|
// mJpegImageReader = null;
|
|
|
|
mJpegImageReader = null;
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
if (null != mRawImageReader) {
|
|
|
|
if (null != mRawImageReader) {
|
|
|
|
mRawImageReader.close();
|
|
|
|
mRawImageReader.close();
|
|
|
|
mRawImageReader = null;
|
|
|
|
mRawImageReader = null;
|
|
|
@ -917,7 +915,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Starts a background thread and its {@link Handler}.
|
|
|
|
* 开启一个后台线程
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
private void startBackgroundThread() {
|
|
|
|
private void startBackgroundThread() {
|
|
|
|
mBackgroundThread = new HandlerThread("CameraBackground");
|
|
|
|
mBackgroundThread = new HandlerThread("CameraBackground");
|
|
|
@ -962,7 +960,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
mPreviewRequestBuilder.addTarget(surface);
|
|
|
|
mPreviewRequestBuilder.addTarget(surface);
|
|
|
|
|
|
|
|
|
|
|
|
// Here, we create a CameraCaptureSession for camera preview.
|
|
|
|
// Here, we create a CameraCaptureSession for camera preview.
|
|
|
|
mCameraDevice.createCaptureSession(Arrays.asList(surface, mRawImageReader.get().getSurface()), new CameraCaptureSession.StateCallback() {
|
|
|
|
mCameraDevice.createCaptureSession(Arrays.asList(surface, mJpegImageReader.get().getSurface(), mRawImageReader.get().getSurface()), new CameraCaptureSession.StateCallback() {
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
|
|
|
|
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
|
|
|
|
synchronized (mCameraStateLock) {
|
|
|
|
synchronized (mCameraStateLock) {
|
|
|
@ -1088,7 +1086,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// For still image captures, we always use the largest available size.
|
|
|
|
// For still image captures, we always use the largest available size.
|
|
|
|
Size largestJpeg = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea());
|
|
|
|
Size largestJpeg = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)), new CompareSizesByArea());
|
|
|
|
|
|
|
|
|
|
|
|
// Find the rotation of the device relative to the native device orientation.
|
|
|
|
// Find the rotation of the device relative to the native device orientation.
|
|
|
|
int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
|
|
|
int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
|
|
@ -1123,8 +1121,8 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Find the best preview size for these view dimensions and configured JPEG size.
|
|
|
|
// Find the best preview size for these view dimensions and configured JPEG size.
|
|
|
|
// Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), rotatedViewWidth, rotatedViewHeight, maxPreviewWidth, maxPreviewHeight, largestJpeg);
|
|
|
|
Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), rotatedViewWidth, rotatedViewHeight, maxPreviewWidth, maxPreviewHeight, largestJpeg);
|
|
|
|
Size previewSize = new Size(3840,2160);
|
|
|
|
// Size previewSize = new Size(3840,2160);
|
|
|
|
|
|
|
|
|
|
|
|
if (swappedDimensions) {
|
|
|
|
if (swappedDimensions) {
|
|
|
|
mTextureView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
|
|
|
|
mTextureView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
|
|
|
@ -1246,7 +1244,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
// This is the CaptureRequest.Builder that we use to take a picture.
|
|
|
|
// This is the CaptureRequest.Builder that we use to take a picture.
|
|
|
|
final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
|
|
|
|
final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
|
|
|
|
|
|
|
|
|
|
|
|
// captureBuilder.addTarget(mJpegImageReader.get().getSurface());
|
|
|
|
captureBuilder.addTarget(mJpegImageReader.get().getSurface());
|
|
|
|
captureBuilder.addTarget(mRawImageReader.get().getSurface());
|
|
|
|
captureBuilder.addTarget(mRawImageReader.get().getSurface());
|
|
|
|
|
|
|
|
|
|
|
|
// Use the same AE and AF modes as the preview.
|
|
|
|
// Use the same AE and AF modes as the preview.
|
|
|
@ -1267,7 +1265,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
|
|
|
|
|
|
|
|
// mCharacteristics.get(CameraMetadata.CONTROL_AE_COMPENSATION_STEP)
|
|
|
|
// mCharacteristics.get(CameraMetadata.CONTROL_AE_COMPENSATION_STEP)
|
|
|
|
List<CaptureRequest> requests = new ArrayList<>();
|
|
|
|
List<CaptureRequest> requests = new ArrayList<>();
|
|
|
|
for (int idx = 0; idx < 10; idx++) {
|
|
|
|
for (int idx = 0; idx < 2; idx++) {
|
|
|
|
// Set request tag to easily track results in callbacks.
|
|
|
|
// Set request tag to easily track results in callbacks.
|
|
|
|
captureBuilder.setTag(mRequestCounter.getAndIncrement());
|
|
|
|
captureBuilder.setTag(mRequestCounter.getAndIncrement());
|
|
|
|
|
|
|
|
|
|
|
@ -1278,7 +1276,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
if (isHandTakePic) {
|
|
|
|
if (isHandTakePic) {
|
|
|
|
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
|
|
|
|
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
|
|
|
|
if (exposureTime > 0) {
|
|
|
|
if (exposureTime > 0) {
|
|
|
|
double v = exposureTime * pic1;
|
|
|
|
double v = exposureTime;
|
|
|
|
captureBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, (long) v);
|
|
|
|
captureBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, (long) v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sensitivity > 0) {
|
|
|
|
if (sensitivity > 0) {
|
|
|
@ -1294,7 +1292,7 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
if (isHandTakePic) {
|
|
|
|
if (isHandTakePic) {
|
|
|
|
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
|
|
|
|
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
|
|
|
|
if (exposureTime > 0) {
|
|
|
|
if (exposureTime > 0) {
|
|
|
|
double v = exposureTime * 2;
|
|
|
|
double v = exposureTime * pic1;
|
|
|
|
captureBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, (long) v);
|
|
|
|
captureBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, (long) v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sensitivity > 0) {
|
|
|
|
if (sensitivity > 0) {
|
|
|
@ -1548,6 +1546,115 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void yuvToRgb(byte[] yuvData, int width, int height, int[] rgbData) {
|
|
|
|
|
|
|
|
int frameSize = width * height;
|
|
|
|
|
|
|
|
int uOffset = frameSize;
|
|
|
|
|
|
|
|
int vOffset = frameSize + frameSize / 4;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
|
|
|
|
|
|
for (int x = 0; x < width; x++) {
|
|
|
|
|
|
|
|
int Y = yuvData[y * width + x] & 0xFF;
|
|
|
|
|
|
|
|
int U = yuvData[uOffset + (y / 2) * (width / 2) + (x / 2)] & 0xFF;
|
|
|
|
|
|
|
|
int V = yuvData[vOffset + (y / 2) * (width / 2) + (x / 2)] & 0xFF;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// YUV to RGB conversion
|
|
|
|
|
|
|
|
int R = (int) (Y + 1.402 * (V - 128));
|
|
|
|
|
|
|
|
int G = (int) (Y - 0.344 * (U - 128) - 0.714 * (V - 128));
|
|
|
|
|
|
|
|
int B = (int) (Y + 1.772 * (U - 128));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Clamp values to [0, 255]
|
|
|
|
|
|
|
|
R = Math.min(255, Math.max(0, R));
|
|
|
|
|
|
|
|
G = Math.min(255, Math.max(0, G));
|
|
|
|
|
|
|
|
B = Math.min(255, Math.max(0, B));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Pack RGB into an integer
|
|
|
|
|
|
|
|
rgbData[y * width + x] = (0xFF << 24) | (R << 16) | (G << 8) | B;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void saveRgbToBmp(File file, int width, int height, int[] rgbData) {
|
|
|
|
|
|
|
|
try (FileOutputStream outputStream = new FileOutputStream(file)) {
|
|
|
|
|
|
|
|
int rowSize = (width * 3 + 3) & ~3; // 每行对齐到 4 字节
|
|
|
|
|
|
|
|
int imageSize = rowSize * height; // 图像数据大小
|
|
|
|
|
|
|
|
int fileSize = 54 + imageSize; // 文件总大小
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// BMP 文件头
|
|
|
|
|
|
|
|
outputStream.write('B');
|
|
|
|
|
|
|
|
outputStream.write('M');
|
|
|
|
|
|
|
|
writeInt(outputStream, fileSize); // 文件大小
|
|
|
|
|
|
|
|
writeInt(outputStream, 0); // 保留字段
|
|
|
|
|
|
|
|
writeInt(outputStream, 54); // 像素数据偏移量
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// BMP 信息头
|
|
|
|
|
|
|
|
writeInt(outputStream, 40); // 信息头大小
|
|
|
|
|
|
|
|
writeInt(outputStream, width); // 图像宽度
|
|
|
|
|
|
|
|
writeInt(outputStream, height); // 图像高度
|
|
|
|
|
|
|
|
writeShort(outputStream, 1); // 颜色平面数
|
|
|
|
|
|
|
|
writeShort(outputStream, 24); // 每像素位数
|
|
|
|
|
|
|
|
writeInt(outputStream, 0); // 压缩方式
|
|
|
|
|
|
|
|
writeInt(outputStream, imageSize);// 图像大小
|
|
|
|
|
|
|
|
writeInt(outputStream, 0); // 水平分辨率
|
|
|
|
|
|
|
|
writeInt(outputStream, 0); // 垂直分辨率
|
|
|
|
|
|
|
|
writeInt(outputStream, 0); // 调色板颜色数
|
|
|
|
|
|
|
|
writeInt(outputStream, 0); // 重要颜色数
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 像素数据
|
|
|
|
|
|
|
|
byte[] row = new byte[rowSize];
|
|
|
|
|
|
|
|
for (int y = height - 1; y >= 0; y--) { // BMP 是从下到上存储的
|
|
|
|
|
|
|
|
for (int x = 0; x < width; x++) {
|
|
|
|
|
|
|
|
int pixel = rgbData[y * width + x];
|
|
|
|
|
|
|
|
row[x * 3] = (byte) (pixel & 0xFF); // B
|
|
|
|
|
|
|
|
row[x * 3 + 1] = (byte) ((pixel >> 8) & 0xFF); // G
|
|
|
|
|
|
|
|
row[x * 3 + 2] = (byte) ((pixel >> 16) & 0xFF); // R
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
outputStream.write(row);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Log.d("BmpUtils", "BMP saved to: " + file.getAbsolutePath());
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void writeShort(FileOutputStream outputStream, int value) throws IOException {
|
|
|
|
|
|
|
|
outputStream.write(value & 0xFF);
|
|
|
|
|
|
|
|
outputStream.write((value >> 8) & 0xFF);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void writeInt(FileOutputStream outputStream, int value) throws IOException {
|
|
|
|
|
|
|
|
outputStream.write(value & 0xFF);
|
|
|
|
|
|
|
|
outputStream.write((value >> 8) & 0xFF);
|
|
|
|
|
|
|
|
outputStream.write((value >> 16) & 0xFF);
|
|
|
|
|
|
|
|
outputStream.write((value >> 24) & 0xFF);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void saveYuvAsBmp(Image image, File file) {
|
|
|
|
|
|
|
|
if (image.getFormat() != ImageFormat.YUV_420_888) {
|
|
|
|
|
|
|
|
throw new IllegalArgumentException("Invalid image format");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int width = image.getWidth();
|
|
|
|
|
|
|
|
int height = image.getHeight();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 提取 YUV 数据
|
|
|
|
|
|
|
|
ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
|
|
|
|
|
|
|
|
ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
|
|
|
|
|
|
|
|
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
byte[] yuvData = new byte[yBuffer.remaining() + uBuffer.remaining() + vBuffer.remaining()];
|
|
|
|
|
|
|
|
yBuffer.get(yuvData, 0, yBuffer.remaining());
|
|
|
|
|
|
|
|
uBuffer.get(yuvData, yBuffer.remaining(), uBuffer.remaining());
|
|
|
|
|
|
|
|
vBuffer.get(yuvData, yBuffer.remaining() + uBuffer.remaining(), vBuffer.remaining());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 将 YUV 转换为 RGB
|
|
|
|
|
|
|
|
int[] rgbData = new int[width * height];
|
|
|
|
|
|
|
|
yuvToRgb(yuvData, width, height, rgbData);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 将 RGB 保存为 BMP
|
|
|
|
|
|
|
|
saveRgbToBmp(file, width, height, rgbData);
|
|
|
|
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Runnable that saves an {@link Image} into the specified {@link File}, and updates
|
|
|
|
* Runnable that saves an {@link Image} into the specified {@link File}, and updates
|
|
|
|
* {@link android.provider.MediaStore} to include the resulting file.
|
|
|
|
* {@link android.provider.MediaStore} to include the resulting file.
|
|
|
@ -1617,6 +1724,12 @@ public class Camera2RawFragment extends Fragment {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case ImageFormat.YUV_420_888: {
|
|
|
|
|
|
|
|
saveYuvAsBmp(mImage, mFile);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
case ImageFormat.RAW_SENSOR: {
|
|
|
|
case ImageFormat.RAW_SENSOR: {
|
|
|
|
DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult);
|
|
|
|
DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult);
|
|
|
|
FileOutputStream output = null;
|
|
|
|
FileOutputStream output = null;
|
|
|
|