/* The contents of this file are subject to the Mozilla Public License Version 1.1 (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.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is collection of files collectively known as Open Camera. The Initial Developer of the Original Code is Almalence Inc. Portions created by Initial Developer are Copyright (C) 2013 by Almalence Inc. All Rights Reserved. */ package com.almalence.plugins.capture.expobracketing; import java.util.Date; import android.annotation.TargetApi; import android.content.SharedPreferences; import android.hardware.camera2.CaptureResult; import android.os.CountDownTimer; import android.preference.PreferenceManager; import android.util.Log; /* <!-- +++ import com.almalence.opencam_plus.ApplicationScreen; import com.almalence.opencam_plus.PluginCapture; import com.almalence.opencam_plus.PluginManager; import com.almalence.opencam_plus.R; import com.almalence.opencam_plus.ui.GUI.CameraParameter; import com.almalence.opencam_plus.cameracontroller.CameraController; import com.almalence.opencam_plus.ApplicationInterface; import com.almalence.opencam_plus.CameraParameters; +++ --> */ // <!-- -+- import com.almalence.opencam.CameraParameters; import com.almalence.opencam.ApplicationScreen; import com.almalence.opencam.PluginCapture; import com.almalence.opencam.PluginManager; import com.almalence.opencam.ApplicationInterface; import com.almalence.opencam.cameracontroller.CameraController; import com.almalence.opencam.ui.GUI.CameraParameter; import com.almalence.opencam.R; //-+- --> /*** * Implements capture plugin with exposure bracketing. Used for HDR image * processing ***/ public class ExpoBracketingCapturePlugin extends PluginCapture { private static final int MAX_HDR_FRAMES = 4; private int preferenceEVCompensationValue; // almashot - related public static int[] evValues = new int[MAX_HDR_FRAMES]; public static int[] evIdx = new int[MAX_HDR_FRAMES]; private int frame_num; public static float ev_step; private boolean cm7_crap; // shared between activities public static int CapIdx; public static int total_frames; public static boolean LumaAdaptationAvailable = false; // preferences public static boolean RefocusPreference; public static boolean UseLumaAdaptation; private int preferenceSceneMode; private int preferenceFlashMode; // set exposure based on onpreviewframe private CountDownTimer cdt = null; private static String sEvPref; private static String sRefocusPref; private static String sUseLumaPref; private boolean camera2Preference; public ExpoBracketingCapturePlugin() { super("com.almalence.plugins.expobracketingcapture", R.xml.preferences_capture_expobracketing, R.xml.preferences_capture_expobracketing, 0, null); } private static String EvPreference; @Override public void onCreate() { sEvPref = ApplicationScreen.getAppResources().getString(R.string.Preference_ExpoBracketingPref); sRefocusPref = ApplicationScreen.getAppResources().getString(R.string.Preference_ExpoBracketingRefocusPref); sUseLumaPref = ApplicationScreen.getAppResources().getString(R.string.Preference_ExpoBracketingUseLumaPref); } @Override public void onStart() { getPrefs(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext()); camera2Preference = prefs.getBoolean(ApplicationScreen.getMainContext().getResources().getString(R.string.Preference_UseCamera2Key), false); // if(CameraController.isFlex2 && camera2Preference) // { // prefs.edit().putBoolean(ApplicationScreen.getMainContext().getResources().getString(R.string.Preference_UseCamera2Key), false).commit(); // CameraController.setUseCamera2(false); // // CameraController.isOldCameraOneModeLaunched = true; // PluginManager.getInstance().setSwitchModeType(true); // } } @Override public void onResume() { inCapture = false; aboutToTakePicture = false; isAllImagesTaken = false; isAllCaptureResultsCompleted = true; ApplicationScreen.instance.muteShutter(false); preferenceEVCompensationValue = ApplicationScreen.instance.getEVPref(); preferenceSceneMode = ApplicationScreen.instance.getSceneModePref(); preferenceFlashMode = ApplicationScreen.instance.getFlashModePref(ApplicationScreen.sDefaultFlashValue); if (CameraController.isUseCamera2() && CameraController.isNexus5or6) ApplicationScreen.instance.setFlashModePref(CameraParameters.FLASH_MODE_OFF); cdt = null; if (PluginManager.getInstance().getActiveModeID().equals("hdrmode")) ApplicationScreen.setCaptureFormat(CameraController.YUV); else if(captureRAW && CameraController.isRAWCaptureSupported()) ApplicationScreen.setCaptureFormat(CameraController.RAW); else { captureRAW = false; ApplicationScreen.setCaptureFormat(CameraController.JPEG); } } @Override public void onPause() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext()); ApplicationScreen.instance.setEVPref(preferenceEVCompensationValue); ApplicationScreen.instance.setSceneModePref(preferenceSceneMode); if (CameraController.isUseCamera2() && CameraController.isNexus5or6) { prefs.edit().putInt(ApplicationScreen.sFlashModePref, preferenceFlashMode).commit(); CameraController.setCameraFlashMode(preferenceFlashMode); } prefs.edit().putBoolean(ApplicationScreen.getMainContext().getResources().getString(R.string.Preference_UseCamera2Key), camera2Preference).commit(); } @Override public void onStop() { // if(CameraController.isFlex2 && camera2Preference) // { // CameraController.useCamera2OnRelaunch(true); // CameraController.setUseCamera2(camera2Preference); // } } @Override public void onGUICreate() { ApplicationScreen.instance.disableCameraParameter(CameraParameter.CAMERA_PARAMETER_EV, true, false, true); ApplicationScreen.instance.disableCameraParameter(CameraParameter.CAMERA_PARAMETER_SCENE, true, true, true); if (CameraController.isUseCamera2() && CameraController.isNexus5or6) ApplicationScreen.instance.disableCameraParameter(CameraParameter.CAMERA_PARAMETER_FLASH, true, false, true); } public boolean delayedCaptureSupported() { return true; } @Override public void setupCameraParameters() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext()); int jpegQuality = Integer.parseInt(prefs.getString(ApplicationScreen.sJPEGQualityPref, "95")); CameraController.Size imageSize = CameraController.getCameraImageSize(); CameraController.setPictureSize(imageSize.getWidth(), imageSize.getHeight()); CameraController.setJpegQuality(jpegQuality); // CameraController.applyCameraParameters(); try { int[] flashModes = CameraController.getSupportedFlashModes(); if (flashModes != null && flashModes.length > 0 && CameraController.isUseCamera2() && CameraController.isNexus5or6) { SharedPreferences.Editor editor = prefs.edit(); editor.putInt(ApplicationScreen.sFlashModePref, CameraParameters.FLASH_MODE_OFF); editor.commit(); CameraController.setCameraFlashMode(CameraParameters.FLASH_MODE_OFF); } int[] sceneModes = CameraController.getSupportedSceneModes(); if (sceneModes != null && CameraController.isModeAvailable(sceneModes, CameraParameters.SCENE_MODE_AUTO)) { CameraController.setCameraSceneMode(CameraParameters.SCENE_MODE_AUTO); ApplicationScreen.instance.setSceneModePref(CameraParameters.SCENE_MODE_AUTO); } } catch (RuntimeException e) { Log.e("ExpoBracketing", "ApplicationScreen.setupCamera unable to setSceneMode"); } CameraController.resetExposureCompensation(); ApplicationScreen.instance.setEVPref(0); } public void onShutterClick() { if (!inCapture) { Date curDate = new Date(); SessionID = curDate.getTime(); cdt = null; startCaptureSequence(); } } private void startCaptureSequence() { ApplicationScreen.instance.muteShutter(true); if (!inCapture) { inCapture = true; // reiniting for every shutter press frame_num = 0; resultCompleted = 0; if (CameraController.isAutoFocusPerform()) aboutToTakePicture = true; else CaptureFrame(); } } @Override public void addToSharedMemExifTags(byte[] frameData) { if (frameData != null) { if (PluginManager.getInstance().getActiveModeID().equals("hdrmode")) { PluginManager.getInstance().addToSharedMemExifTagsFromJPEG(frameData, SessionID, -1); } else { PluginManager.getInstance().addToSharedMemExifTagsFromJPEG(frameData, SessionID, frame_num + 1); } } else if (frame_num == 0) { PluginManager.getInstance().addToSharedMemExifTagsFromCamera(SessionID); } } @Override public void onImageTaken(int frame, byte[] frameData, int frame_len, int format) { int n = evIdx[frame_num]; if (cm7_crap && (total_frames == 3)) { if (frame_num == 0) n = evIdx[0]; else if (frame_num == 1) n = evIdx[2]; else n = evIdx[1]; } boolean isRAW = (format == CameraController.RAW); if (isRAW) { imagesTakenRAW++; PluginManager.getInstance().addToSharedMem("frame" + (imagesTakenRAW + 3) + SessionID, String.valueOf(frame)); PluginManager.getInstance().addToSharedMem("framelen" + (imagesTakenRAW + 3) + SessionID, String.valueOf(frame_len)); PluginManager.getInstance().addToSharedMem("frameorientation" + (imagesTakenRAW + 3) + SessionID, String.valueOf(ApplicationScreen.getGUIManager().getImageDataOrientation())); PluginManager.getInstance().addToSharedMem("framemirrored" + (imagesTakenRAW + 3) + SessionID, String.valueOf(CameraController.isFrontCamera())); PluginManager.getInstance().addToSharedMem("frameisraw" + (imagesTakenRAW + 3) + SessionID, String.valueOf(isRAW)); PluginManager.getInstance().addToSharedMem("amountofcapturedrawframes" + SessionID, String.valueOf(imagesTakenRAW)); } else { ++frame_num; PluginManager.getInstance().addToSharedMem("frame" + (n + 1) + SessionID, String.valueOf(frame)); PluginManager.getInstance().addToSharedMem("framelen" + (n + 1) + SessionID, String.valueOf(frame_len)); PluginManager.getInstance().addToSharedMem("frameorientation" + (n + 1) + SessionID, String.valueOf(ApplicationScreen.getGUIManager().getImageDataOrientation())); PluginManager.getInstance().addToSharedMem("framemirrored" + (n + 1) + SessionID, String.valueOf(CameraController.isFrontCamera())); PluginManager.getInstance().addToSharedMem("frameisraw" + (n + 1) + SessionID, String.valueOf(isRAW)); PluginManager.getInstance().addToSharedMem("amountofcapturedframes" + SessionID, String.valueOf(n + 1)); } if ((captureRAW && (frame_num + imagesTakenRAW) >= (total_frames * 2)) || (!captureRAW && frame_num >= total_frames)) { if(isAllCaptureResultsCompleted) { PluginManager.getInstance().addToSharedMem("amountofcapturedframes" + SessionID, String.valueOf(frame_num + imagesTakenRAW)); if (cdt != null) { cdt.cancel(); cdt = null; } PluginManager.getInstance().sendMessage(ApplicationInterface.MSG_CAPTURE_FINISHED, String.valueOf(SessionID)); CameraController.resetExposureCompensation(); imagesTakenRAW = 0; frame_num = 0; resultCompleted = 0; inCapture = false; isAllImagesTaken = false; } else isAllImagesTaken = true; } } @TargetApi(21) @Override public void onCaptureCompleted(CaptureResult result) { isAllCaptureResultsCompleted = false; int requestID = requestIDArray[resultCompleted]; resultCompleted++; if (result.getSequenceId() == requestID) { // Log.e("ExpoBkt", "frame_num = " + frame_num); // if (evIdx[frame_num] == 0) PluginManager.getInstance().addToSharedMemExifTagsFromCaptureResult(result, SessionID, resultCompleted); } if (captureRAW) { Log.e("ExpoBkt", "resultCompleted = " + resultCompleted); PluginManager.getInstance().addRAWCaptureResultToSharedMem("captureResult" + resultCompleted + SessionID, result); } if ((captureRAW && resultCompleted >= (total_frames * 2)) || (!captureRAW && resultCompleted >= total_frames)) { isAllCaptureResultsCompleted = true; resultCompleted = 0; if(isAllImagesTaken) { PluginManager.getInstance().addToSharedMem("amountofcapturedframes" + SessionID, String.valueOf(frame_num + imagesTakenRAW)); if (cdt != null) { cdt.cancel(); cdt = null; } PluginManager.getInstance().sendMessage(ApplicationInterface.MSG_CAPTURE_FINISHED, String.valueOf(SessionID)); CameraController.resetExposureCompensation(); imagesTakenRAW = 0; frame_num = 0; resultCompleted = 0; inCapture = false; isAllImagesTaken = false; } } } @Override public void onExportFinished() { } private void getPrefs() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext()); RefocusPreference = prefs.getBoolean(sRefocusPref, false); UseLumaAdaptation = prefs.getBoolean(sUseLumaPref, false); EvPreference = prefs.getString(sEvPref, "0"); captureRAW = prefs.getBoolean(ApplicationScreen.sCaptureRAWPref, false); if (PluginManager.getInstance().getActiveModeID().equals("hdrmode")) captureRAW = false; } @Override public void onCameraSetup() { // ----- Figure expo correction parameters FindExpoParameters(); } void FindExpoParameters() { int ev_inc; int min_ev, max_ev; LumaAdaptationAvailable = CameraController.isLumaAdaptationSupported(); if (UseLumaAdaptation && LumaAdaptationAvailable) { // set up fixed values for luma-adaptation (used on Qualcomm // chipsets) ev_step = 0.5f; total_frames = 3; evIdx[0] = 0; evIdx[1] = 1; evIdx[2] = 2; evValues[0] = 8; evValues[1] = 4; evValues[2] = 0; return; } // figure min and max ev min_ev = CameraController.getMinExposureCompensation(); max_ev = CameraController.getMaxExposureCompensation(); try { ev_step = CameraController.getExposureCompensationStep(); } catch (NullPointerException e) { // miezu m9 fails to provide exposure correction step, // substituting with the most common step ev_step = 0.5f; } // cyanogenmod returns values that are absolutely ridiculous // change to a more sensible values, which at least return differing // exposures cm7_crap = false; if (ev_step > 1) { // debug log ev_step = 0.5f; cm7_crap = true; } // motorola droid2 crap (likely other motorola models too) - step is // clearly not what is reported // signature: <motorola> <DROID2>, ev_step = 0.3333 if (android.os.Build.MANUFACTURER.toLowerCase().contains("motorola") && (Math.abs(ev_step - 0.333) < 0.01)) ev_step = 1.5f; // xperia cameras seem to give slightly higher step than reported by // android if (android.os.Build.MANUFACTURER.toLowerCase().contains("sony") && (Math.abs(ev_step - 0.333) < 0.01)) ev_step = 0.5f; // incorrect step in GT-S5830, may be other samsung models if (android.os.Build.MANUFACTURER.toLowerCase().contains("samsung") && (Math.abs(ev_step - 0.166) < 0.01)) ev_step = 0.5f; switch (Integer.parseInt(EvPreference)) { case 1: // -1 to +1 Ev compensation max_ev = (int) Math.floor(1 / ev_step); min_ev = -max_ev; break; case 2: // -2 to +2 Ev compensation max_ev = (int) Math.floor(2 / ev_step); min_ev = -max_ev; break; default: break; } // select proper min_ev, ev_inc if (ev_step == 0) { min_ev = 0; max_ev = 0; ev_inc = 0; total_frames = 3; for (int i = 0; i < total_frames; ++i) evValues[i] = 0; } else { ev_inc = (int) Math.floor(2 / ev_step); // we do not need overly wide dynamic range, limit to [-3Ev..+3Ev] // some models report range that they can not really handle if ((min_ev * ev_step < -3) && (max_ev * ev_step > 3) && PluginManager.getInstance().getActiveModeID().equals("hdrmode")) { max_ev = (int) Math.floor(3 / ev_step); min_ev = -max_ev; } CameraController.Size imageSize = CameraController.getCameraImageSize(); // if capturing more than 5mpix images - force no more than 3 frames int max_total_frames = MAX_HDR_FRAMES; if (imageSize.getWidth() * imageSize.getHeight() > 5200000) max_total_frames = 3; // motorola likes it a lot when the first shot is at 0Ev // (it will not change exposure on consequent shots otherwise) // Ev=0 total_frames = 1; int min_range = 0; int max_range = 0; if ((ev_inc <= max_ev) && (total_frames < max_total_frames)) { max_range = 1; ++total_frames; } if ((-ev_inc >= min_ev) && (total_frames < max_total_frames)) { min_range = -1; ++total_frames; } if ((2 * ev_inc <= max_ev) && (total_frames < max_total_frames)) { max_range = 2; ++total_frames; } if ((-2 * ev_inc >= min_ev) && (total_frames < max_total_frames)) { min_range = -2; ++total_frames; } // if the range is too small for reported Ev step - just do two // frames - at min Ev and at max Ev if (max_range == min_range) { total_frames = 2; evValues[0] = max_ev; evValues[1] = min_ev; } else { evValues[0] = 0; int frame = 1; for (int i = max_range; i >= min_range; --i) if (i != 0) { evValues[frame] = i * ev_inc; ++frame; } } } // sort frame idx'es in descending order of Ev's boolean[] skip_idx = new boolean[MAX_HDR_FRAMES]; for (int i = 0; i < total_frames; ++i) skip_idx[i] = false; for (int i = 0; i < total_frames; ++i) { int ev_max = min_ev - 1; int max_idx = 0; for (int j = 0; j < total_frames; ++j) if ((evValues[j] > ev_max) && (!skip_idx[j])) { ev_max = evValues[j]; max_idx = j; } evIdx[max_idx] = i; skip_idx[max_idx] = true; } } public void NotEnoughMemory() { // // warn user of low memory } public void CaptureFrame() { boolean isHDRMode = PluginManager.getInstance().getActiveModeID().equals("hdrmode"); // requestID = CameraController.captureImagesWithParams(total_frames, // isHDRMode? CameraController.YUV : CameraController.JPEG, new int[0], // evValues, true); int gain[] = null; long exposure[] = null; // On Nexus5x and Nexus6p we can't change exposure compensation. // That's why we will use manual exposure (shutter speed) settings to capture HDR. // Adjust the shutter speed one or two stops faster (i.e. if you're at 1/250 sec, go to 1/500 or 1/1000 sec), take a photo, // then adjust it one or two stops slower than your original shutter speed // (i.e. if you were at 1/250 sec, then set it to 1/125 or 1/60 sec), and take another photo. // Also if we use manual exposure settings, we should set ISO manually (get ISO and Exposure original values from preview). if (CameraController.isNexus5x || CameraController.isNexus6p || CameraController.isFlex2 || CameraController.isGalaxyS7 || CameraController.isG5|| CameraController.isHTCM10|| CameraController.isMotoZ) { gain = new int[3]; gain[0] = CameraController.getCurrentSensitivity(); gain[1] = CameraController.getCurrentSensitivity(); gain[2] = CameraController.getCurrentSensitivity(); switch (Integer.parseInt(EvPreference)) { case 1: // -1 to +1 Ev compensation exposure = new long[3]; exposure[0] = CameraController.getCameraExposureTime(); exposure[1] = CameraController.getCameraExposureTime() * 2; exposure[2] = CameraController.getCameraExposureTime() / 2; break; case 2: // -2 to +2 Ev compensation exposure = new long[3]; exposure[0] = CameraController.getCameraExposureTime(); exposure[1] = CameraController.getCameraExposureTime() * 4; exposure[2] = CameraController.getCameraExposureTime() / 4; break; default: exposure = new long[3]; exposure[0] = CameraController.getCameraExposureTime(); if (CameraController.isGalaxyS7) { exposure[1] = CameraController.getCameraExposureTime() * 2; exposure[2] = CameraController.getCameraExposureTime() / 4; } else { exposure[1] = CameraController.getCameraExposureTime() * 4; exposure[2] = CameraController.getCameraExposureTime() / 2; } break; } } createRequestIDList(captureRAW? total_frames*2 : total_frames); if (captureRAW) CameraController.captureImagesWithParams(total_frames, CameraController.RAW, null, evValues, gain, exposure, false, true, true); else CameraController.captureImagesWithParams(total_frames, isHDRMode ? CameraController.YUV : CameraController.JPEG, null, evValues, gain, exposure, false, true, true); } public void onAutoFocus(boolean paramBoolean) { if (inCapture) // disregard autofocus success (paramBoolean) { // Log.d("HDR", "onAutoFocus inCapture == true"); // on motorola xt5 cm7 this function is called twice! // on motorola droid's onAutoFocus seem to be called at every // startPreview, // causing additional frame(s) taken after sequence is finished if (aboutToTakePicture) CaptureFrame(); aboutToTakePicture = false; } } // onPreviewFrame is used only to provide an exact delay between setExposure // and takePicture @Override public void onPreviewFrame(byte[] data) { } public boolean photoTimeLapseCaptureSupported() { return true; } }