Commit 2a3a1658 authored by jinx's avatar jinx

'初始化'

parents
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/cordova.plugin.camera.iml" filepath="$PROJECT_DIR$/.idea/cordova.plugin.camera.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="ce9823c8-c749-4218-9165-fb0170b5369a" name="Default" comment="" />
<ignored path="$PROJECT_DIR$/.tmp/" />
<ignored path="$PROJECT_DIR$/temp/" />
<ignored path="$PROJECT_DIR$/tmp/" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="ProjectFrameBounds">
<option name="x" value="327" />
<option name="y" value="2" />
<option name="width" value="1045" />
<option name="height" value="745" />
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
<manualOrder />
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scope" />
<pane id="ProjectPane" />
<pane id="Scratches" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="ShelveChangesManager" show_recycled="false">
<option name="remove_strategy" value="false" />
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="ce9823c8-c749-4218-9165-fb0170b5369a" name="Default" comment="" />
<created>1558687997996</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1558687997996</updated>
<workItem from="1558687999225" duration="18000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="18000" />
</component>
<component name="ToolWindowManager">
<frame x="327" y="2" width="1045" height="745" extended-state="0" />
<layout>
<window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.24925521" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Docker" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
<watches-manager />
</component>
</project>
\ No newline at end of file
function takePicture()
{
cordova.plugins.camera.takePicture({"isCorp":true},success , error);
}
isCorp : 是否裁剪
true 裁剪
false 不裁剪
function success(path)
{
path : 图片路径
}
function error(msg)
{
msg : 错误内容
}
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
id="cordova.plugin.camera" version="0.0.1">
<name>cameraPlugin</name>
<author>camera</author>
<description>camera</description>
<keywords>camera</keywords>
<license>Apache 2.0</license>
<js-module charset="utf-8" name="camera" src="www/camera.js">
<clobbers target="cordova.plugins.camera" />
</js-module>
<platform name="android">
<config-file parent="/*" target="res/xml/config.xml">
<feature name="camera">
<param name="android-package" value="com.cordova.camera.CameraPlugin"/>
</feature>
</config-file>
<source-file src="src/android/CameraPlugin.java" target-dir="src/com/cordova/camera"/>
<source-file src="src/android/util/DimensionUtil.java" target-dir="src/com/cordova/camera/util"/>
<source-file src="src/android/util/FileUtil.java" target-dir="src/com/cordova/camera/util"/>
<source-file src="src/android/util/IDUtil.java" target-dir="src/com/cordova/camera/util"/>
<source-file src="src/android/util/ImageUtil.java" target-dir="src/com/cordova/camera/util"/>
<source-file src="src/android/view/CameraActivity.java" target-dir="src/com/cordova/camera/view"/>
<source-file src="src/android/view/CameraControl.java" target-dir="src/com/cordova/camera/view"/>
<source-file src="src/android/view/CameraLayout.java" target-dir="src/com/cordova/camera/view"/>
<source-file src="src/android/view/CameraThreadPool.java" target-dir="src/com/cordova/camera/view"/>
<source-file src="src/android/view/CameraView.java" target-dir="src/com/cordova/camera/view"/>
<source-file src="src/android/view/ICameraControl.java" target-dir="src/com/cordova/camera/view"/>
<source-file src="src/android/view/MaskView.java" target-dir="src/com/cordova/camera/view"/>
<source-file src="src/android/view/PermissionCallback.java" target-dir="src/com/cordova/camera/view"/>
<source-file src="src/android/layout/activity_camera.xml" target-dir="res/layout"/>
<source-file src="src/android/layout/confirm_result.xml" target-dir="res/layout"/>
<source-file src="src/android/layout/take_picture.xml" target-dir="res/layout"/>
<source-file src="src/android/drawable/camera_switch.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/cancel.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/close.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/confirm.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/gallery.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/hint_align_bank_card.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/id_card_locator_front.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/light_off.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/light_on.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/rotate.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/take_photo_highlight.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/take_photo_normal.png" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/round_corner.xml" target-dir="res/drawable-hdpi"/>
<source-file src="src/android/drawable/take_photo_selector.xml" target-dir="res/drawable-hdpi"/>
<!--
<source-file src="src/android/lib/android-support-v4.jar" target-dir="libs/" />
-->
<framework src="com.android.support:support-v4:24.1.1+" />
<config-file target="res/values/styles.xml" parent="/resources">
<declare-styleable name="CameraLayout">
<attr name="contentView" format="reference"/>
<attr name="centerView" format="reference"/>
<attr name="leftDownView" format="reference"/>
<attr name="rightUpView" format="reference"/>
</declare-styleable>
</config-file>
<config-file parent="/manifest" target="AndroidManifest.xml">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera2.full" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
</config-file>
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<activity
android:name="com.cordova.camera.view.CameraActivity"
android:configChanges="screenSize|orientation"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" />
</config-file>
</platform>
</plugin>
package com.cordova.camera;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.cordova.camera.util.FileUtil;
import com.cordova.camera.view.CameraActivity;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by xiang.wang on 2017/4/14.
*/
public class CameraPlugin extends CordovaPlugin {
private static final String ACTION_TAKE_PICTURE = "takePicture";
private static final int REQUEST_CODE_CAMERA = 102;
private CallbackContext mCallbackContext;
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
boolean isCorp = true;
if(args != null && args.length() > 0){
JSONObject object = args.optJSONObject(0);
if(object != null){
isCorp = object.optBoolean("isCorp");
}
}
if(ACTION_TAKE_PICTURE.equals(action)){
Intent intent = new Intent(cordova.getActivity(), CameraActivity.class);
intent.putExtra(CameraActivity.KEY_OUTPUT_FILE_PATH, FileUtil.getSaveFile(cordova.getActivity()).getAbsolutePath());
intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, CameraActivity.CONTENT_TYPE_ID_CARD_FRONT);
intent.putExtra("isCrop", isCorp);
cordova.startActivityForResult(CameraPlugin.this,intent, REQUEST_CODE_CAMERA);
}
mCallbackContext = callbackContext;
return true;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == REQUEST_CODE_CAMERA && resultCode == Activity.RESULT_OK) {
if (intent != null) {
String path = FileUtil.getSaveFile(cordova.getActivity()).getAbsolutePath();
if(mCallbackContext != null){
if(!path.isEmpty()){
mCallbackContext.success(path);
}else{
mCallbackContext.success("取消拍照");
}
}
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#000000" />
<corners android:radius="12dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按压时 -->
<item android:drawable="@drawable/take_photo_highlight" android:state_pressed="true" />
<!-- 被选中时 -->
<item android:drawable="@drawable/take_photo_normal" android:state_selected="true" />
<!-- 被激活时 -->
<item android:drawable="@drawable/take_photo_normal" android:state_activated="true" />
<!-- 默认时 -->
<item android:drawable="@drawable/take_photo_normal" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/take_picture_container"
layout="@layout/take_picture"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:id="@+id/confirm_result_container"
layout="@layout/confirm_result"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<com.cordova.camera.view.CameraLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentView="@+id/display_image_view"
app:leftDownView="@+id/cancel_button"
app:rightUpView="@+id/confirm_button">
<ImageView
android:id="@+id/display_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/id_card_locator_front" />
<ImageView
android:id="@+id/cancel_button"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_marginBottom="16dp"
android:layout_marginLeft="18dp"
android:layout_marginStart="18dp"
android:padding="12dp"
android:src="@drawable/cancel" />
<ImageView
android:id="@+id/confirm_button"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_marginEnd="18dp"
android:layout_marginRight="18dp"
android:layout_marginTop="16dp"
android:padding="12dp"
android:src="@drawable/confirm" />
</com.cordova.camera.view.CameraLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000000">
<ImageView
android:id="@+id/iv_light_button"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginEnd="18dp"
android:layout_marginRight="18dp"
android:clickable="true"
android:padding="15dp"
android:paddingBottom="3dp"
android:src="@drawable/light_off"
tools:ignore="RtlHardcoded" />
</RelativeLayout>
<com.cordova.camera.view.CameraLayout
android:id="@+id/cameralayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:centerView="@+id/take_photo_button"
app:contentView="@+id/camera_view"
app:leftDownView="@+id/album_button"
app:rightUpView="@+id/camera_switch">
<com.cordova.camera.view.CameraView
android:id="@+id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/album_button"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_marginBottom="16dp"
android:layout_marginLeft="18dp"
android:layout_marginStart="18dp"
android:padding="12dp"
android:src="@drawable/cancel" />
<ImageView
android:id="@+id/take_photo_button"
android:layout_width="58dp"
android:layout_height="58dp"
android:background="@drawable/take_photo_selector"
android:clickable="true" />
<ImageView
android:id="@+id/camera_switch"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginEnd="18dp"
android:layout_marginRight="18dp"
android:layout_marginTop="16dp"
android:clickable="true"
android:padding="15dp"
android:paddingBottom="3dp"
android:src="@drawable/camera_switch" />
</com.cordova.camera.view.CameraLayout>
</LinearLayout>
package com.cordova.camera.util;
import android.content.res.Resources;
public class DimensionUtil {
public static int dpToPx(int dp) {
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
}
package com.cordova.camera.util;
import android.content.Context;
import java.io.File;
public class FileUtil {
public static File getSaveFile(Context context) {
File file = new File(context.getFilesDir(), "pic.jpg");
return file;
}
}
package com.cordova.camera.util;
import android.content.Context;
import java.lang.reflect.Field;
/**
* Created by USER on 2019/5/24.
*/
public class IDUtil {
public static int getIdByNameAndType(Context context,String type,String name){
return context.getResources().getIdentifier(name, type, context.getPackageName()) ;
}
/**
* 对于context.getResources().getIdentifier无法获取的数据,或者数组
* 资源反射值
* @paramcontext
* @param name
* @param type
* @return
*/
private static Object getResourceId(Context context,String name, String type) {
String className = context.getPackageName() +".R";
try {
Class cls = Class.forName(className);
for (Class childClass : cls.getClasses()) {
String simple = childClass.getSimpleName();
if (simple.equals(type)) {
for (Field field : childClass.getFields()) {
String fieldName = field.getName();
if (fieldName.equals(name)) {
System.out.println(fieldName);
return field.get(null);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
*context.getResources().getIdentifier无法获取到styleable的数据
* @paramcontext
* @param name
* @return
*/
public static int getStyleable(Context context, String name) {
return ((Integer)getResourceId(context, name,"styleable")).intValue();
}
/**
* 获取styleable的ID号数组
* @paramcontext
* @param name
* @return
*/
public static int[] getStyleableArray(Context context,String name) {
return (int[])getResourceId(context, name,"styleable");
}
}
package com.cordova.camera.util;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.util.Log;
public class ImageUtil {
private static final String TAG = "CameraExif";
public static int exifToDegrees(int exifOrientation) {
if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
return 90;
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {
return 180;
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {
return 270;
}
return 0;
}
// Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
public static int getOrientation(byte[] jpeg) {
if (jpeg == null) {
return 0;
}
int offset = 0;
int length = 0;
// ISO/IEC 10918-1:1993(E)
while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
int marker = jpeg[offset] & 0xFF;
// Check if the marker is a padding.
if (marker == 0xFF) {
continue;
}
offset++;
// Check if the marker is SOI or TEM.
if (marker == 0xD8 || marker == 0x01) {
continue;
}
// Check if the marker is EOI or SOS.
if (marker == 0xD9 || marker == 0xDA) {
break;
}
// Get the length and check if it is reasonable.
length = pack(jpeg, offset, 2, false);
if (length < 2 || offset + length > jpeg.length) {
Log.e(TAG, "Invalid length");
return 0;
}
// Break if the marker is EXIF in APP1.
if (marker == 0xE1 && length >= 8
&& pack(jpeg, offset + 2, 4, false) == 0x45786966
&& pack(jpeg, offset + 6, 2, false) == 0) {
offset += 8;
length -= 8;
break;
}
// Skip other markers.
offset += length;
length = 0;
}
// JEITA CP-3451 Exif Version 2.2
if (length > 8) {
// Identify the byte order.
int tag = pack(jpeg, offset, 4, false);
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
Log.e(TAG, "Invalid byte order");
return 0;
}
boolean littleEndian = (tag == 0x49492A00);
// Get the offset and check if it is reasonable.
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
if (count < 10 || count > length) {
Log.e(TAG, "Invalid offset");
return 0;
}
offset += count;
length -= count;
// Get the count and go through all the elements.
count = pack(jpeg, offset - 2, 2, littleEndian);
while (count-- > 0 && length >= 12) {
// Get the tag and check if it is orientation.
tag = pack(jpeg, offset, 2, littleEndian);
if (tag == 0x0112) {
// We do not really care about type and count, do we?
int orientation = pack(jpeg, offset + 8, 2, littleEndian);
switch (orientation) {
case 1:
return 0;
case 3:
return 180;
case 6:
return 90;
case 8:
return 270;
default:
return 0;
}
}
offset += 12;
length -= 12;
}
}
Log.i(TAG, "Orientation not found");
return 0;
}
private static int pack(byte[] bytes, int offset, int length,
boolean littleEndian) {
int step = 1;
if (littleEndian) {
offset += length - 1;
step = -1;
}
int value = 0;
while (length-- > 0) {
value = (value << 8) | (bytes[offset] & 0xFF);
offset += step;
}
return value;
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
<resources>
<declare-styleable name="CameraLayout">
<attr name="contentView" format="reference"/>
<attr name="centerView" format="reference"/>
<attr name="leftDownView" format="reference"/>
<attr name="rightUpView" format="reference"/>
</declare-styleable>
</resources>
package com.cordova.camera.view;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.view.Surface;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.cordova.camera.util.IDUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class CameraActivity extends Activity {
public static final String KEY_OUTPUT_FILE_PATH = "outputFilePath";
public static final String KEY_CONTENT_TYPE = "contentType";
public static final String CONTENT_TYPE_ID_CARD_FRONT = "IDCardFront";
private static final int REQUEST_CODE_PICK_IMAGE = 100;
private static final int PERMISSIONS_REQUEST_CAMERA = 800;
private static final int PERMISSIONS_EXTERNAL_STORAGE = 801;
private File outputFile;
private String contentType;
private Handler handler = new Handler();
private LinearLayout takePictureContainer;
private CameraLayout confirmResultContainer;
private ImageView cameraSwitch;
private CameraView cameraView;
private ImageView displayImageView;
private ImageView takePhotoBtn;
private PermissionCallback permissionCallback = new PermissionCallback() {
@Override
public boolean onRequestPermission() {
ActivityCompat.requestPermissions(CameraActivity.this,
new String[]{Manifest.permission.CAMERA},
PERMISSIONS_REQUEST_CAMERA);
return false;
}
};
private CameraLayout mCameraLayout;
private ImageView mIvLightButton;
private boolean mIsCrop;//是否自动截图
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(IDUtil.getIdByNameAndType(CameraActivity.this, "layout", "activity_camera"));
takePictureContainer = (LinearLayout) findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "take_picture_container"));
mCameraLayout = (CameraLayout) findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "cameralayout"));
mIvLightButton = (ImageView) findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "iv_light_button"));
mIvLightButton.setOnClickListener(lightButtonOnClickListener);
confirmResultContainer = (CameraLayout) findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "confirm_result_container"));
cameraView = (CameraView) findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "camera_view"));
cameraView.getCameraControl().setPermissionCallback(permissionCallback);
cameraSwitch = (ImageView) findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "camera_switch"));
cameraSwitch.setOnClickListener(cameraSwitchOnClickListener);
takePhotoBtn = (ImageView) findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "take_photo_button"));
findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "album_button")).setOnClickListener(albumButtonOnClickListener);
takePhotoBtn.setOnClickListener(takeButtonOnClickListener);
// confirm result;
displayImageView = (ImageView) findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "display_image_view"));
confirmResultContainer.findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "confirm_button")).setOnClickListener(confirmButtonOnClickListener);
confirmResultContainer.findViewById(IDUtil.getIdByNameAndType(CameraActivity.this, "id", "cancel_button")).setOnClickListener(confirmCancelButtonOnClickListener);
setOrientation(getResources().getConfiguration());
initParams();
cameraView.setAutoPictureCallback(autoTakePictureCallback);
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onPause() {
super.onPause();
cameraView.stop();
}
@Override
protected void onResume() {
super.onResume();
cameraView.start();
}
private void initParams() {
String outputPath = getIntent().getStringExtra(KEY_OUTPUT_FILE_PATH);
if (outputPath != null) {
outputFile = new File(outputPath);
}
contentType = getIntent().getStringExtra(KEY_CONTENT_TYPE);
int maskType;
if (contentType.equals(CONTENT_TYPE_ID_CARD_FRONT)) {
maskType = MaskView.MASK_TYPE_ID_CARD_FRONT;
} else {
maskType = MaskView.MASK_TYPE_NONE;
}
mIsCrop = getIntent().getBooleanExtra("isCrop", true);
cameraView.setMaskType(maskType, this);
}
private void showTakePicture() {
cameraView.getCameraControl().resume();
updateFlashMode();
takePictureContainer.setVisibility(View.VISIBLE);
confirmResultContainer.setVisibility(View.INVISIBLE);
}
private void showCrop() {
cameraView.getCameraControl().pause();
updateFlashMode();
takePictureContainer.setVisibility(View.INVISIBLE);
confirmResultContainer.setVisibility(View.INVISIBLE);
}
private void showResultConfirm() {
cameraView.getCameraControl().pause();
updateFlashMode();
takePictureContainer.setVisibility(View.INVISIBLE);
confirmResultContainer.setVisibility(View.VISIBLE);
}
// take photo;
private void updateFlashMode() {
int flashMode = cameraView.getCameraControl().getFlashMode();
if (flashMode == ICameraControl.FLASH_MODE_TORCH) {
mIvLightButton.setImageResource(IDUtil.getIdByNameAndType(CameraActivity.this, "drawable", "light_on"));
} else {
mIvLightButton.setImageResource(IDUtil.getIdByNameAndType(CameraActivity.this, "drawable", "light_off"));
}
}
private View.OnClickListener albumButtonOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
};
private View.OnClickListener lightButtonOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (cameraView.getCameraControl().getFlashMode() == ICameraControl.FLASH_MODE_OFF) {
if (cameraView.getCameraControl().isSupportFlashMode()) {
cameraView.getCameraControl().setFlashMode(ICameraControl.FLASH_MODE_TORCH);
} else {
Toast.makeText(CameraActivity.this, "当前相机不支持该闪光灯模式", Toast.LENGTH_SHORT).show();
}
} else {
cameraView.getCameraControl().setFlashMode(ICameraControl.FLASH_MODE_OFF);
}
updateFlashMode();
}
};
private View.OnClickListener cameraSwitchOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
int flashMode = cameraView.getCameraControl().getFlashMode();
if (flashMode == ICameraControl.FLASH_MODE_TORCH) {
mIvLightButton.setImageResource(IDUtil.getIdByNameAndType(CameraActivity.this, "drawable", "light_off"));
}
cameraView.switchCamera();
}
};
private View.OnClickListener takeButtonOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
cameraView.takePicture(outputFile, takePictureCallback, mIsCrop);
}
};
private CameraView.OnTakePictureCallback autoTakePictureCallback = new CameraView.OnTakePictureCallback() {
@Override
public void onPictureTaken(final Bitmap bitmap) {
CameraThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
bitmap.recycle();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
Intent intent = new Intent();
intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, contentType);
setResult(Activity.RESULT_OK, intent);
finish();
}
});
}
};
private CameraView.OnTakePictureCallback takePictureCallback = new CameraView.OnTakePictureCallback() {
@Override
public void onPictureTaken(final Bitmap bitmap) {
handler.post(new Runnable() {
@Override
public void run() {
takePictureContainer.setVisibility(View.INVISIBLE);
displayImageView.setImageBitmap(bitmap);
showResultConfirm();
}
});
}
};
private void doConfirmResult() {
CameraThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
Bitmap bitmap = ((BitmapDrawable) displayImageView.getDrawable()).getBitmap();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
Intent intent = new Intent();
intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, contentType);
setResult(Activity.RESULT_OK, intent);
finish();
}
});
}
private View.OnClickListener confirmButtonOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
doConfirmResult();
}
};
private View.OnClickListener confirmCancelButtonOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
displayImageView.setImageBitmap(null);
showTakePicture();
}
};
private String getRealPathFromURI(Uri contentURI) {
String result;
Cursor cursor = null;
try {
cursor = getContentResolver().query(contentURI, null, null, null, null);
} catch (Throwable e) {
e.printStackTrace();
}
if (cursor == null) {
result = contentURI.getPath();
} else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(idx);
cursor.close();
}
return result;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setOrientation(newConfig);
}
private void setOrientation(Configuration newConfig) {
int rotation = getWindowManager().getDefaultDisplay().getRotation();
int orientation;
int cameraViewOrientation = CameraView.ORIENTATION_PORTRAIT;
switch (newConfig.orientation) {
case Configuration.ORIENTATION_PORTRAIT:
cameraViewOrientation = CameraView.ORIENTATION_PORTRAIT;
orientation = CameraLayout.ORIENTATION_PORTRAIT;
break;
case Configuration.ORIENTATION_LANDSCAPE:
orientation = CameraLayout.ORIENTATION_HORIZONTAL;
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
cameraViewOrientation = CameraView.ORIENTATION_HORIZONTAL;
} else {
cameraViewOrientation = CameraView.ORIENTATION_INVERT;
}
break;
default:
orientation = CameraLayout.ORIENTATION_PORTRAIT;
cameraView.setOrientation(CameraView.ORIENTATION_PORTRAIT);
break;
}
mCameraLayout.setOrientation(orientation);
cameraView.setOrientation(cameraViewOrientation);
confirmResultContainer.setOrientation(orientation);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_PICK_IMAGE) {
if (resultCode == Activity.RESULT_OK) {
Uri uri = data.getData();
showCrop();
} else {
cameraView.getCameraControl().resume();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSIONS_REQUEST_CAMERA: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
cameraView.getCameraControl().refreshPermission();
} else {
Toast.makeText(getApplicationContext(), "本功能需要相机权限", Toast.LENGTH_LONG)
.show();
}
break;
}
case PERMISSIONS_EXTERNAL_STORAGE:
default:
break;
}
}
/**
* 做一些收尾工作
*/
private void doClear() {
CameraThreadPool.cancelAutoFocusTimer();
}
@Override
protected void onDestroy() {
super.onDestroy();
this.doClear();
}
}
package com.cordova.camera.view;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.view.TextureView;
import android.view.View;
import android.widget.FrameLayout;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 5.0以下相机API的封装。
*/
@SuppressWarnings("deprecation")
public class CameraControl implements ICameraControl {
private int displayOrientation = 0;
private int cameraId = 0;
private int flashMode;
private AtomicBoolean takingPicture = new AtomicBoolean(false);
private AtomicBoolean abortingScan = new AtomicBoolean(false);
private Context context;
private Camera camera;
private Camera.Parameters parameters;
private PermissionCallback permissionCallback;
private Rect previewFrame = new Rect();
private PreviewView previewView;
private View displayView;
private int rotation = 0;
private OnDetectPictureCallback detectCallback;
private int previewFrameCount = 0;
private Camera.Size optSize;
/*
* 非扫描模式
*/
private final int MODEL_NOSCAN = 0;
/*
* 本地质量控制扫描模式
*/
private final int MODEL_SCAN = 1;
private int detectType = MODEL_NOSCAN;
public int getCameraRotation() {
return rotation;
}
public AtomicBoolean getAbortingScan() {
return abortingScan;
}
@Override
public void setDetectCallback(OnDetectPictureCallback callback) {
detectType = MODEL_SCAN;
detectCallback = callback;
}
private void onRequestDetect(byte[] data) {
// 相机已经关闭
if (camera == null || data == null || optSize == null) {
return;
}
YuvImage img = new YuvImage(data, ImageFormat.NV21, optSize.width, optSize.height, null);
ByteArrayOutputStream os = null;
try {
os = new ByteArrayOutputStream(data.length);
img.compressToJpeg(new Rect(0, 0, optSize.width, optSize.height), 80, os);
byte[] jpeg = os.toByteArray();
int status = detectCallback.onDetect(jpeg, getCameraRotation());
if (status == 0) {
clearPreviewCallback();
}
} catch (OutOfMemoryError e) {
// 内存溢出则取消当次操作
} finally {
try {
os.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
@Override
public void setDisplayOrientation(@CameraView.Orientation int displayOrientation) {
this.displayOrientation = displayOrientation;
switch (displayOrientation) {
case CameraView.ORIENTATION_PORTRAIT:
rotation = 90;
break;
case CameraView.ORIENTATION_HORIZONTAL:
rotation = 0;
break;
case CameraView.ORIENTATION_INVERT:
rotation = 180;
break;
default:
rotation = 0;
}
previewView.requestLayout();
}
/**
* {@inheritDoc}
*/
@Override
public void refreshPermission() {
startPreview(true);
}
/**
* {@inheritDoc}
*/
@Override
public void setFlashMode(@FlashMode int flashMode) {
if (this.flashMode == flashMode) {
return;
}
this.flashMode = flashMode;
updateFlashMode(flashMode);
}
@Override
public int getFlashMode() {
return flashMode;
}
/**
* 获取当前相机支持的闪光灯模式(是否支持Camera.Parameters.FLASH_MODE_TORCH模式)
*
* @return
*/
@Override
public boolean isSupportFlashMode() {
List<String> flashModes = camera.getParameters().getSupportedFlashModes();
if (flashModes != null) {
for (String mode : flashModes) {
if (TextUtils.equals(Camera.Parameters.FLASH_MODE_TORCH, mode)) {
return true;
}
}
}
return false;
}
@Override
public void start() {
startPreview(false);
}
@Override
public void stop() {
if (camera != null) {
camera.setPreviewCallback(null);
stopPreview();
// 避免同步代码,为了先设置null后release
Camera tempC = camera;
camera = null;
tempC.release();
camera = null;
buffer = null;
}
}
@Override
public void switchCamera() {
switch (cameraId) {
case Camera.CameraInfo.CAMERA_FACING_BACK:
stop();
cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
try {
if (camera == null) {
try {
camera = Camera.open(cameraId);
} catch (Throwable e) {
e.printStackTrace();
startPreview(true);
return;
}
}
parameters = camera.getParameters();
opPreviewSize(previewView.getWidth(), previewView.getHeight());
camera.setPreviewTexture(surfaceCache);
setPreviewCallbackImpl();
startPreview(false);
} catch (IOException e) {
e.printStackTrace();
}
break;
case Camera.CameraInfo.CAMERA_FACING_FRONT:
stop();
cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
try {
if (camera == null) {
try {
camera = Camera.open(cameraId);
} catch (Throwable e) {
e.printStackTrace();
startPreview(true);
return;
}
}
parameters = camera.getParameters();
parameters.setPreviewFormat(ImageFormat.NV21);
opPreviewSize(previewView.getWidth(), previewView.getHeight());
camera.setPreviewTexture(surfaceCache);
setPreviewCallbackImpl();
startPreview(false);
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
private void stopPreview() {
if (camera != null) {
camera.stopPreview();
}
}
@Override
public void pause() {
if (camera != null) {
stopPreview();
}
setFlashMode(FLASH_MODE_OFF);
}
@Override
public void resume() {
takingPicture.set(false);
if (camera == null) {
openCamera();
} else {
previewView.textureView.setSurfaceTextureListener(surfaceTextureListener);
if (previewView.textureView.isAvailable()) {
startPreview(false);
}
}
}
@Override
public View getDisplayView() {
return displayView;
}
@Override
public void takePicture(final OnTakePictureCallback onTakePictureCallback, final boolean isCrop) {
if (takingPicture.get()) {
return;
}
switch (displayOrientation) {
case CameraView.ORIENTATION_PORTRAIT:
parameters.setRotation(90);
break;
case CameraView.ORIENTATION_HORIZONTAL:
parameters.setRotation(0);
break;
case CameraView.ORIENTATION_INVERT:
parameters.setRotation(180);
break;
}
if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {//前置摄像头照片翻转270°
parameters.setRotation(270);
}
try {
Camera.Size picSize = getOptimalSize(camera.getParameters().getSupportedPictureSizes());
parameters.setPictureSize(picSize.width, picSize.height);
camera.setParameters(parameters);
takingPicture.set(true);
cancelAutoFocus();
CameraThreadPool.execute(new Runnable() {
@Override
public void run() {
camera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
startPreview(false);
takingPicture.set(false);
if (onTakePictureCallback != null) {
onTakePictureCallback.onPictureTaken(data, cameraId, isCrop);
}
}
});
}
});
} catch (RuntimeException e) {
e.printStackTrace();
startPreview(false);
takingPicture.set(false);
}
}
@Override
public void setPermissionCallback(PermissionCallback callback) {
this.permissionCallback = callback;
}
public CameraControl(Context context) {
this.context = context;
previewView = new PreviewView(context);
openCamera();
}
private void openCamera() {
setupDisplayView();
}
private void setupDisplayView() {
final TextureView textureView = new TextureView(context);
previewView.textureView = textureView;
previewView.setTextureView(textureView);
displayView = previewView;
textureView.setSurfaceTextureListener(surfaceTextureListener);
}
private SurfaceTexture surfaceCache;
private byte[] buffer = null;
private void setPreviewCallbackImpl() {
if (buffer == null) {
buffer = new byte[displayView.getWidth()
* displayView.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8];
}
if (camera != null && detectType == MODEL_SCAN) {
camera.addCallbackBuffer(buffer);
camera.setPreviewCallback(previewCallback);
}
}
private void clearPreviewCallback() {
if (camera != null && detectType == MODEL_SCAN) {
camera.setPreviewCallback(null);
stopPreview();
}
}
Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(final byte[] data, Camera camera) {
// 扫描成功阻止打开新线程处理
if (abortingScan.get()) {
return;
}
// 节流
if (previewFrameCount++ % 5 != 0) {
return;
}
// 在某些机型和某项项目中,某些帧的data的数据不符合nv21的格式,需要过滤,否则后续处理会导致crash
if (data.length != parameters.getPreviewSize().width * parameters.getPreviewSize().height * 1.5) {
return;
}
camera.addCallbackBuffer(buffer);
CameraThreadPool.execute(new Runnable() {
@Override
public void run() {
CameraControl.this.onRequestDetect(data);
}
});
}
};
private void initCamera() {
try {
if (camera == null) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
cameraId = i;
}
}
try {
camera = Camera.open(cameraId);
} catch (Throwable e) {
e.printStackTrace();
startPreview(true);
return;
}
}
if (parameters == null) {
parameters = camera.getParameters();
parameters.setPreviewFormat(ImageFormat.NV21);
}
opPreviewSize(previewView.getWidth(), previewView.getHeight());
camera.setPreviewTexture(surfaceCache);
setPreviewCallbackImpl();
startPreview(false);
} catch (IOException e) {
e.printStackTrace();
}
}
private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
surfaceCache = surface;
initCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
opPreviewSize(previewView.getWidth(), previewView.getHeight());
startPreview(false);
setPreviewCallbackImpl();
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
setPreviewCallbackImpl();
}
};
// 开启预览
private void startPreview(boolean checkPermission) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
if (checkPermission && permissionCallback != null) {
permissionCallback.onRequestPermission();
}
return;
}
if (camera == null) {
initCamera();
} else {
camera.startPreview();
startAutoFocus();
}
}
private void cancelAutoFocus() {
camera.cancelAutoFocus();
CameraThreadPool.cancelAutoFocusTimer();
}
private void startAutoFocus() {
CameraThreadPool.createAutoFocusTimerTask(new Runnable() {
@Override
public void run() {
synchronized (CameraControl.this) {
if (camera != null && !takingPicture.get()) {
try {
camera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
}
});
} catch (Throwable e) {
// startPreview是异步实现,可能在某些机器上前几次调用会autofocus failß
}
}
}
}
});
}
private void opPreviewSize(int width, @SuppressWarnings("unused") int height) {
if (parameters != null && camera != null && width > 0) {
optSize = getOptimalSize(camera.getParameters().getSupportedPreviewSizes());
parameters.setPreviewSize(optSize.width, optSize.height);
previewView.setRatio(1.0f * optSize.width / optSize.height);
camera.setDisplayOrientation(getSurfaceOrientation());
stopPreview();
try {
camera.setParameters(parameters);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
}
private Camera.Size getOptimalSize(List<Camera.Size> sizes) {
int width = previewView.textureView.getWidth();
int height = previewView.textureView.getHeight();
Camera.Size pictureSize = sizes.get(0);
List<Camera.Size> candidates = new ArrayList<Camera.Size>();
for (Camera.Size size : sizes) {
if (size.width >= width && size.height >= height && size.width * height == size.height * width) {
// 比例相同
candidates.add(size);
} else if (size.height >= width && size.width >= height && size.width * width == size.height * height) {
// 反比例
candidates.add(size);
}
}
if (!candidates.isEmpty()) {
return Collections.min(candidates, sizeComparator);
}
for (Camera.Size size : sizes) {
if (size.width > width && size.height > height) {
return size;
}
}
return pictureSize;
}
private Comparator<Camera.Size> sizeComparator = new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
return Long.signum((long) lhs.width * lhs.height - (long) rhs.width * rhs.height);
}
};
private void updateFlashMode(int flashMode) {
switch (flashMode) {
case FLASH_MODE_TORCH:
if (isSupportFlashMode()) {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
}
break;
case FLASH_MODE_OFF:
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
break;
case ICameraControl.FLASH_MODE_AUTO:
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
break;
default:
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
break;
}
this.camera.setParameters(parameters);
}
private int getSurfaceOrientation() {
@CameraView.Orientation
int orientation = displayOrientation;
switch (orientation) {
case CameraView.ORIENTATION_PORTRAIT:
return 90;
case CameraView.ORIENTATION_HORIZONTAL:
return 0;
case CameraView.ORIENTATION_INVERT:
return 180;
default:
return 90;
}
}
/**
* 有些相机匹配不到完美的比例。比如。我们的layout是4:3的。预览只有16:9
* 的,如果直接显示图片会拉伸,变形。缩放的话,又有黑边。所以我们采取的策略
* 是,等比例放大。这样预览的有一部分会超出屏幕。拍照后再进行裁剪处理。
*/
private class PreviewView extends FrameLayout {
private TextureView textureView;
private float ratio = 0.75f;
void setTextureView(TextureView textureView) {
this.textureView = textureView;
removeAllViews();
addView(textureView);
}
void setRatio(float ratio) {
this.ratio = ratio;
requestLayout();
relayout(getWidth(), getHeight());
}
public PreviewView(Context context) {
super(context);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
relayout(w, h);
}
private void relayout(int w, int h) {
int width = w;
int height = h;
if (w < h) {
// 垂直模式,高度固定。
height = (int) (width * ratio);
} else {
// 水平模式,宽度固定。
width = (int) (height * ratio);
}
int l = (getWidth() - width) / 2;
int t = (getHeight() - height) / 2;
previewFrame.left = l;
previewFrame.top = t;
previewFrame.right = l + width;
previewFrame.bottom = t + height;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
textureView.layout(previewFrame.left, previewFrame.top, previewFrame.right, previewFrame.bottom);
}
}
@Override
public Rect getPreviewFrame() {
return previewFrame;
}
}
package com.cordova.camera.view;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.cordova.camera.util.IDUtil;
public class CameraLayout extends FrameLayout {
public static int ORIENTATION_PORTRAIT = 0;
public static int ORIENTATION_HORIZONTAL = 1;
private int orientation = ORIENTATION_PORTRAIT;
private View contentView;
private View centerView;
private View leftDownView;
private View rightUpView;
private int contentViewId;
private int centerViewId;
private int leftDownViewId;
private int rightUpViewId;
private Context mContext;
public void setOrientation(int orientation) {
if (this.orientation == orientation) {
return;
}
this.orientation = orientation;
requestLayout();
}
public CameraLayout(Context context) {
super(context);
mContext = context;
}
public CameraLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
parseAttrs(attrs);
}
public CameraLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
parseAttrs(attrs);
}
{
setWillNotDraw(false);
}
private void parseAttrs(AttributeSet attrs) {
TypedArray a = getContext().getTheme().obtainStyledAttributes(
attrs,
IDUtil.getStyleableArray(mContext,"CameraLayout"),
0, 0);
try {
contentViewId = a.getResourceId(IDUtil.getStyleable(mContext,"CameraLayout_contentView"), -1);
centerViewId = a.getResourceId(IDUtil.getStyleable(mContext,"CameraLayout_centerView"), -1);
leftDownViewId = a.getResourceId(IDUtil.getStyleable(mContext,"CameraLayout_leftDownView"), -1);
rightUpViewId = a.getResourceId(IDUtil.getStyleable(mContext,"CameraLayout_rightUpView"), -1);
} finally {
a.recycle();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
contentView = findViewById(contentViewId);
if (centerViewId != -1) {
centerView = findViewById(centerViewId);
}
leftDownView = findViewById(leftDownViewId);
rightUpView = findViewById(rightUpViewId);
}
private Rect backgroundRect = new Rect();
private Paint paint = new Paint();
{
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.argb(83, 0, 0, 0));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = getWidth();
int height = getHeight();
int left;
int top;
ViewGroup.MarginLayoutParams leftDownViewLayoutParams = (MarginLayoutParams) leftDownView.getLayoutParams();
ViewGroup.MarginLayoutParams rightUpViewLayoutParams = (MarginLayoutParams) rightUpView.getLayoutParams();
if (r < b) {
int contentHeight = width * 7 / 5;
int heightLeft = height - contentHeight;
top = t - dpToPx(60);
contentView.layout(l, top, r, contentHeight);
backgroundRect.left = 0;
backgroundRect.top = contentHeight;
backgroundRect.right = width;
backgroundRect.bottom = height;
// layout centerView;
if (centerView != null) {
left = (width - centerView.getMeasuredWidth()) / 2;
top = contentHeight + (heightLeft - centerView.getMeasuredHeight()) / 2;
centerView
.layout(left, top, left + centerView.getMeasuredWidth(), top + centerView.getMeasuredHeight());
}
// layout leftDownView
left = leftDownViewLayoutParams.leftMargin;
top = contentHeight + (heightLeft - leftDownView.getMeasuredHeight()) / 2;
leftDownView
.layout(left, top, left + leftDownView.getMeasuredWidth(), top + leftDownView.getMeasuredHeight());
// layout rightUpView
left = width - rightUpView.getMeasuredWidth() - rightUpViewLayoutParams.rightMargin;
top = contentHeight + (heightLeft - rightUpView.getMeasuredHeight()) / 2;
rightUpView.layout(left, top, left + rightUpView.getMeasuredWidth(), top + rightUpView.getMeasuredHeight());
} else {
int contentWidth = height * 4 / 3;
int widthLeft = width - contentWidth;
contentView.layout(l, t, contentWidth, height);
backgroundRect.left = contentWidth;
backgroundRect.top = 0;
backgroundRect.right = width;
backgroundRect.bottom = height;
// layout centerView
if (centerView != null) {
left = contentWidth + (widthLeft - centerView.getMeasuredWidth()) / 2;
top = (height - centerView.getMeasuredHeight()) / 2;
centerView
.layout(left, top, left + centerView.getMeasuredWidth(), top + centerView.getMeasuredHeight());
}
// layout leftDownView
left = contentWidth + (widthLeft - leftDownView.getMeasuredWidth()) / 2;
top = height - leftDownView.getMeasuredHeight() - leftDownViewLayoutParams.bottomMargin;
leftDownView
.layout(left, top, left + leftDownView.getMeasuredWidth(), top + leftDownView.getMeasuredHeight());
// layout rightUpView
left = contentWidth + (widthLeft - rightUpView.getMeasuredWidth()) / 2;
top = rightUpViewLayoutParams.topMargin;
rightUpView.layout(left, top, left + rightUpView.getMeasuredWidth(), top + rightUpView.getMeasuredHeight());
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(backgroundRect, paint);
}
public static int dpToPx(int dp) {
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
}
package com.cordova.camera.view;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CameraThreadPool {
static Timer timerFocus = null;
/*
* 对焦频率
*/
static final long cameraScanInterval = 2000;
/*
* 线程池大小
*/
private static int poolCount = Runtime.getRuntime().availableProcessors();
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(poolCount);
/**
* 给线程池添加任务
*
* @param runnable 任务
*/
public static void execute(Runnable runnable) {
fixedThreadPool.execute(runnable);
}
/**
* 创建一个定时对焦的timer任务
*
* @param runnable 对焦代码
* @return Timer Timer对象,用来终止自动对焦
*/
public static Timer createAutoFocusTimerTask(final Runnable runnable) {
if (timerFocus != null) {
return timerFocus;
}
timerFocus = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
runnable.run();
}
};
timerFocus.scheduleAtFixedRate(task, 0, cameraScanInterval);
return timerFocus;
}
/**
* 终止自动对焦任务,实际调用了cancel方法并且清空对象
* 但是无法终止执行中的任务,需额外处理
*/
public static void cancelAutoFocusTimer() {
if (timerFocus != null) {
timerFocus.cancel();
timerFocus = null;
}
}
}
package com.cordova.camera.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.cordova.camera.util.DimensionUtil;
import com.cordova.camera.util.IDUtil;
import com.cordova.camera.util.ImageUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Desc:负责,相机的管理。同时提供,裁剪遮罩功能。
* Created by gyf on 2019/5/18.
*/
public class CameraView extends FrameLayout {
private int maskType;
private boolean isEnableScan;
/**
* 照相回调
*/
interface OnTakePictureCallback {
void onPictureTaken(Bitmap bitmap);
}
/**
* 垂直方向 {@link #setOrientation(int)}
*/
public static final int ORIENTATION_PORTRAIT = 0;
/**
* 水平方向 {@link #setOrientation(int)}
*/
public static final int ORIENTATION_HORIZONTAL = 90;
/**
* 水平翻转方向 {@link #setOrientation(int)}
*/
public static final int ORIENTATION_INVERT = 270;
/**
* 本地模型授权,加载成功
*/
public static final int NATIVE_AUTH_INIT_SUCCESS = 0;
/**
* 本地模型授权,模型加载失败
*/
public static final int NATIVE_INIT_FAIL = 12;
public void setInitNativeStatus(int initNativeStatus) {
this.initNativeStatus = initNativeStatus;
}
/**
* 本地检测初始化,模型加载标识
*/
private int initNativeStatus = NATIVE_AUTH_INIT_SUCCESS;
@IntDef({ORIENTATION_PORTRAIT, ORIENTATION_HORIZONTAL, ORIENTATION_INVERT})
public @interface Orientation {
}
private CameraViewTakePictureCallback cameraViewTakePictureCallback = new CameraViewTakePictureCallback();
private ICameraControl cameraControl;
/**
* 相机预览View
*/
private View displayView;
/**
* 身份证,银行卡,等裁剪用的遮罩
*/
private MaskView maskView;
/**
* 用于显示提示证 "请对齐卡片正面" 之类的背景
*/
private ImageView hintView;
/**
* 用于显示提示证 "请对齐卡片正面" 之类的文字
*/
private TextView hintViewText;
/**
* 提示文案容器
*/
private LinearLayout hintViewTextWrapper;
/**
* UI线程的handler
*/
Handler uiHandler = new Handler(Looper.getMainLooper());
public ICameraControl getCameraControl() {
return cameraControl;
}
public void setOrientation(@Orientation int orientation) {
cameraControl.setDisplayOrientation(orientation);
}
private Context mContext;
public CameraView(Context context) {
super(context);
mContext = context;
init();
}
public CameraView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
public void start() {
cameraControl.start();
setKeepScreenOn(true);
}
public void stop() {
cameraControl.stop();
setKeepScreenOn(false);
}
public void switchCamera() {
cameraControl.switchCamera();
setKeepScreenOn(true);
}
/**
* @param file 文件路径
* @param callback 拍照回调
* @param isCrop 是否自动截图
*/
public void takePicture(final File file, final OnTakePictureCallback callback, boolean isCrop) {
cameraViewTakePictureCallback.file = file;
cameraViewTakePictureCallback.callback = callback;
cameraControl.takePicture(cameraViewTakePictureCallback, isCrop);
}
private OnTakePictureCallback autoPictureCallback;
public void setAutoPictureCallback(OnTakePictureCallback callback) {
autoPictureCallback = callback;
}
public void setMaskType(@MaskView.MaskType int maskType, final Context ctx) {
maskView.setMaskType(maskType);
maskView.setVisibility(VISIBLE);
hintView.setVisibility(VISIBLE);
int hintResourceId = IDUtil.getIdByNameAndType(mContext, "drawable", "hint_align_bank_card");
this.maskType = maskType;
boolean isNeedSetImage = true;
switch (maskType) {
case MaskView.MASK_TYPE_ID_CARD_FRONT:
hintResourceId = IDUtil.getIdByNameAndType(mContext, "drawable", "round_corner");
isNeedSetImage = false;
break;
case MaskView.MASK_TYPE_NONE:
default:
maskView.setVisibility(INVISIBLE);
hintView.setVisibility(INVISIBLE);
break;
}
if (isNeedSetImage) {
hintView.setImageResource(hintResourceId);
hintViewTextWrapper.setVisibility(INVISIBLE);
}
if (maskType == MaskView.MASK_TYPE_ID_CARD_FRONT && isEnableScan) {
cameraControl.setDetectCallback(new ICameraControl.OnDetectPictureCallback() {
@Override
public int onDetect(byte[] data, int rotation) {
return detect(data, rotation);
}
});
}
}
private int detect(byte[] data, final int rotation) {
if (initNativeStatus != NATIVE_AUTH_INIT_SUCCESS) {
showTipMessage(initNativeStatus);
return 1;
}
// 扫描成功阻止多余的操作
if (cameraControl.getAbortingScan().get()) {
return 0;
}
Rect previewFrame = cameraControl.getPreviewFrame();
if (maskView.getWidth() == 0 || maskView.getHeight() == 0
|| previewFrame.width() == 0 || previewFrame.height() == 0) {
return 0;
}
// BitmapRegionDecoder不会将整个图片加载到内存。
BitmapRegionDecoder decoder = null;
try {
decoder = BitmapRegionDecoder.newInstance(data, 0, data.length, true);
} catch (IOException e) {
e.printStackTrace();
}
int width = rotation % 180 == 0 ? decoder.getWidth() : decoder.getHeight();
int height = rotation % 180 == 0 ? decoder.getHeight() : decoder.getWidth();
Rect frameRect = maskView.getFrameRectExtend();
int left = width * frameRect.left / maskView.getWidth();
int top = height * frameRect.top / maskView.getHeight();
int right = width * frameRect.right / maskView.getWidth();
int bottom = height * frameRect.bottom / maskView.getHeight();
// 高度大于图片
if (previewFrame.top < 0) {
// 宽度对齐。
int adjustedPreviewHeight = previewFrame.height() * getWidth() / previewFrame.width();
int topInFrame = ((adjustedPreviewHeight - frameRect.height()) / 2)
* getWidth() / previewFrame.width();
int bottomInFrame = ((adjustedPreviewHeight + frameRect.height()) / 2) * getWidth()
/ previewFrame.width();
// 等比例投射到照片当中。
top = topInFrame * height / previewFrame.height();
bottom = bottomInFrame * height / previewFrame.height();
} else {
// 宽度大于图片
if (previewFrame.left < 0) {
// 高度对齐
int adjustedPreviewWidth = previewFrame.width() * getHeight() / previewFrame.height();
int leftInFrame = ((adjustedPreviewWidth - maskView.getFrameRect().width()) / 2) * getHeight()
/ previewFrame.height();
int rightInFrame = ((adjustedPreviewWidth + maskView.getFrameRect().width()) / 2) * getHeight()
/ previewFrame.height();
// 等比例投射到照片当中。
left = leftInFrame * width / previewFrame.width();
right = rightInFrame * width / previewFrame.width();
}
}
Rect region = new Rect();
region.left = left;
region.top = top;
region.right = right;
region.bottom = bottom;
// 90度或者270度旋转
if (rotation % 180 == 90) {
int x = decoder.getWidth() / 2;
int y = decoder.getHeight() / 2;
int rotatedWidth = region.height();
int rotated = region.width();
// 计算,裁剪框旋转后的坐标
region.left = x - rotatedWidth / 2;
region.top = y - rotated / 2;
region.right = x + rotatedWidth / 2;
region.bottom = y + rotated / 2;
region.sort();
}
BitmapFactory.Options options = new BitmapFactory.Options();
// 最大图片大小。
int maxPreviewImageSize = 2560;
int size = Math.min(decoder.getWidth(), decoder.getHeight());
size = Math.min(size, maxPreviewImageSize);
options.inSampleSize = ImageUtil.calculateInSampleSize(options, size, size);
options.inScaled = true;
options.inDensity = Math.max(options.outWidth, options.outHeight);
options.inTargetDensity = size;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = decoder.decodeRegion(region, options);
if (rotation != 0) {
// 只能是裁剪完之后再旋转了。有没有别的更好的方案呢?
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
Bitmap rotatedBitmap = Bitmap.createBitmap(
bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
if (bitmap != rotatedBitmap) {
// 有时候 createBitmap会复用对象
bitmap.recycle();
}
bitmap = rotatedBitmap;
}
int status = 1;
showTipMessage(status);
return status;
}
private void showTipMessage(final int status) {
// 提示tip文字变化
uiHandler.post(new Runnable() {
@Override
public void run() {
if (status == 0) {
hintViewText.setVisibility(View.INVISIBLE);
} else if (!cameraControl.getAbortingScan().get()) {
hintViewText.setVisibility(View.VISIBLE);
hintViewText.setText(getScanMessage(status));
}
}
});
}
private String getScanMessage(int status) {
String message;
switch (status) {
case 0:
message = "";
break;
case 1:
default:
message = "请对准取景框,不要遮挡";
}
return message;
}
private void init() {
cameraControl = new CameraControl(getContext());
displayView = cameraControl.getDisplayView();
addView(displayView);
maskView = new MaskView(getContext());
addView(maskView);
hintView = new ImageView(getContext());
addView(hintView);
hintViewTextWrapper = new LinearLayout(getContext());
hintViewTextWrapper.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
DimensionUtil.dpToPx(25));
lp.gravity = Gravity.CENTER;
hintViewText = new TextView(getContext());
hintViewText.setBackgroundResource(IDUtil.getIdByNameAndType(mContext, "drawable", "round_corner"));
hintViewText.setAlpha(0.5f);
hintViewText.setPadding(DimensionUtil.dpToPx(10), 0, DimensionUtil.dpToPx(10), 0);
hintViewTextWrapper.addView(hintViewText, lp);
hintViewText.setGravity(Gravity.CENTER);
hintViewText.setTextColor(Color.WHITE);
hintViewText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
hintViewText.setText(getScanMessage(-1));
hintViewText.setVisibility(GONE);
addView(hintViewTextWrapper, lp);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
displayView.layout(left, 0, right, bottom - top);
maskView.layout(left, 0, right, bottom - top);
int hintViewWidth = DimensionUtil.dpToPx(250);
int hintViewHeight = DimensionUtil.dpToPx(25);
int hintViewLeft = (getWidth() - hintViewWidth) / 2;
int hintViewTop = maskView.getFrameRect().bottom + DimensionUtil.dpToPx(16);
hintViewTextWrapper.layout(hintViewLeft, hintViewTop,
hintViewLeft + hintViewWidth, hintViewTop + hintViewHeight);
hintView.layout(hintViewLeft, hintViewTop,
hintViewLeft + hintViewWidth, hintViewTop + hintViewHeight);
}
/**
* 拍摄后的照片。需要进行裁剪。有些手机(比如三星)不会对照片数据进行旋转,而是将旋转角度写入EXIF信息当中,
* 所以需要做旋转处理。
*
* @param outputFile 写入照片的文件。
* @param data 原始照片数据。
* @param rotation 照片exif中的旋转角度。
* @return 裁剪好的bitmap。
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
private Bitmap crop(File outputFile, byte[] data, int rotation) {
try {
Rect previewFrame = cameraControl.getPreviewFrame();
if (maskView.getWidth() == 0 || maskView.getHeight() == 0
|| previewFrame.width() == 0 || previewFrame.height() == 0) {
return null;
}
// BitmapRegionDecoder不会将整个图片加载到内存。
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(data, 0, data.length, true);
int width = rotation % 180 == 0 ? decoder.getWidth() : decoder.getHeight();
int height = rotation % 180 == 0 ? decoder.getHeight() : decoder.getWidth();
// Rect frameRect = maskView.getFrameRect();
Rect frameRect = maskView.frame;
int left = width * frameRect.left / maskView.getWidth();
int top = height * frameRect.top / maskView.getHeight();
int right = width * frameRect.right / maskView.getWidth();
int bottom = height * frameRect.bottom / maskView.getHeight();
// 高度大于图片
if (previewFrame.top < 0) {
// 宽度对齐。
int adjustedPreviewHeight = previewFrame.height() * getWidth() / previewFrame.width();
int topInFrame = ((adjustedPreviewHeight - frameRect.height()) / 2)
* getWidth() / previewFrame.width();
int bottomInFrame = ((adjustedPreviewHeight + frameRect.height()) / 2) * getWidth()
/ previewFrame.width();
// 等比例投射到照片当中。
top = topInFrame * height / previewFrame.height();
bottom = bottomInFrame * height / previewFrame.height();
} else {
// 宽度大于图片
if (previewFrame.left < 0) {
// 高度对齐
int adjustedPreviewWidth = previewFrame.width() * getHeight() / previewFrame.height();
int leftInFrame = ((adjustedPreviewWidth - maskView.getFrameRect().width()) / 2) * getHeight()
/ previewFrame.height();
int rightInFrame = ((adjustedPreviewWidth + maskView.getFrameRect().width()) / 2) * getHeight()
/ previewFrame.height();
// 等比例投射到照片当中。
left = leftInFrame * width / previewFrame.width();
right = rightInFrame * width / previewFrame.width();
}
}
Rect region = new Rect();
region.left = left;
region.top = top;
region.right = right;
region.bottom = bottom;
// 90度或者270度旋转
if (rotation % 180 == 90) {
int x = decoder.getWidth() / 2;
int y = decoder.getHeight() / 2;
int rotatedWidth = region.height();
int rotated = region.width();
// 计算,裁剪框旋转后的坐标
region.left = x - rotatedWidth / 2;
region.top = y - rotated / 2;
region.right = x + rotatedWidth / 2;
region.bottom = y + rotated / 2;
region.sort();
}
BitmapFactory.Options options = new BitmapFactory.Options();
// 最大图片大小。
int maxPreviewImageSize = 2560;
int size = Math.min(decoder.getWidth(), decoder.getHeight());
size = Math.min(size, maxPreviewImageSize);
options.inSampleSize = ImageUtil.calculateInSampleSize(options, size, size);
options.inScaled = true;
options.inDensity = Math.max(options.outWidth, options.outHeight);
options.inTargetDensity = size;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = decoder.decodeRegion(region, options);
if (rotation != 0) {
// 只能是裁剪完之后再旋转了。有没有别的更好的方案呢?
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
Bitmap rotatedBitmap = Bitmap.createBitmap(
bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
if (bitmap != rotatedBitmap) {
// 有时候 createBitmap会复用对象
bitmap.recycle();
}
bitmap = rotatedBitmap;
}
try {
if (!outputFile.exists()) {
outputFile.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
return bitmap;
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private class CameraViewTakePictureCallback implements ICameraControl.OnTakePictureCallback {
private File file;
private OnTakePictureCallback callback;
@Override
public void onPictureTaken(final byte[] data, final int cameraId, final boolean isCrop) {
CameraThreadPool.execute(new Runnable() {
@Override
public void run() {
final int rotation = ImageUtil.getOrientation(data);
// BitmapFactory.Options options = new BitmapFactory.Options();
// options.inJustDecodeBounds = true;
Bitmap bitmap;
if (isCrop) {
bitmap = crop(file, data, rotation);
} else {
bitmap = getBitmapFromBytes(data, null);
}
if (cameraId == 1) {//前置摄像头拍的照片做镜像处理
Bitmap coverBitmap = coverBitmap(bitmap);
callback.onPictureTaken(coverBitmap);
} else {
callback.onPictureTaken(bitmap);
}
}
});
}
}
private Bitmap getBitmapFromBytes(byte[] bytes, BitmapFactory.Options options) {
if (bytes != null)
if (options != null) {
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
} else {
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
return null;
}
/**
* 前置摄像头图片镜像翻转
*/
private Bitmap coverBitmap(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(-1, 1);
Bitmap coverBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
return coverBitmap;
}
}
package com.cordova.camera.view;
import android.graphics.Rect;
import android.support.annotation.IntDef;
import android.view.View;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Desc:Android 5.0 相机的API发生很大的变化。些类屏蔽掉了 api的变化。相机的操作和功能,抽象剥离出来。
* Created by gyf on 2019/5/18.
*/
public interface ICameraControl {
/**
* 闪光灯关 {@link #setFlashMode(int)}
*/
int FLASH_MODE_OFF = 0;
/**
* 闪光灯开 {@link #setFlashMode(int)}
*/
int FLASH_MODE_TORCH = 1;
/**
* 闪光灯自动 {@link #setFlashMode(int)}
*/
int FLASH_MODE_AUTO = 2;
@IntDef({FLASH_MODE_TORCH, FLASH_MODE_OFF, FLASH_MODE_AUTO})
@interface FlashMode {
}
/**
* 照相回调。
*/
interface OnTakePictureCallback {
void onPictureTaken(byte[] data, int cameraId, boolean isCrop);
}
/**
* 设置本地质量控制回调,如果不设置则视为不扫描调用本地质量控制代码。
*/
void setDetectCallback(OnDetectPictureCallback callback);
/**
* 预览回调
*/
interface OnDetectPictureCallback {
int onDetect(byte[] data, int rotation);
}
/**
* 打开相机。
*/
void start();
/**
* 关闭相机
*/
void stop();
/**
* 切换摄像头
*/
void switchCamera();
void pause();
void resume();
/**
* 相机对应的预览视图。
* @return 预览视图
*/
View getDisplayView();
/**
* 看到的预览可能不是照片的全部。返回预览视图的全貌。
* @return 预览视图frame;
*/
Rect getPreviewFrame();
/**
* 拍照。结果在回调中获取。
* @param callback 拍照结果回调
* @param isCrop
*/
void takePicture(OnTakePictureCallback callback, boolean isCrop);
/**
* 设置权限回调,当手机没有拍照权限时,可在回调中获取。
* @param callback 权限回调
*/
void setPermissionCallback(PermissionCallback callback);
/**
* 设置水平方向
* @param displayOrientation 参数值见 {@link CameraView.Orientation}
*/
void setDisplayOrientation(@CameraView.Orientation int displayOrientation);
/**
* 获取到拍照权限时,调用些函数以继续。
*/
void refreshPermission();
/**
* 获取已经扫描成功,处理中
*/
AtomicBoolean getAbortingScan();
/**
* 设置闪光灯状态。
* @param flashMode {@link #FLASH_MODE_TORCH,#FLASH_MODE_OFF,#FLASH_MODE_AUTO}
*/
void setFlashMode(@FlashMode int flashMode);
/**
* 获取当前闪光灯状态
* @return 当前闪光灯状态 参见 {@link #setFlashMode(int)}
*/
@FlashMode
int getFlashMode();
/**
* 获取当前相机支持的闪光灯模式(是否支持Camera.Parameters.FLASH_MODE_TORCH模式)
*/
boolean isSupportFlashMode();
}
package com.cordova.camera.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.view.View;
import java.io.File;
public class MaskView extends View {
public static final int MASK_TYPE_NONE = 0;
public static final int MASK_TYPE_ID_CARD_FRONT = 1;
@IntDef({MASK_TYPE_NONE, MASK_TYPE_ID_CARD_FRONT})
@interface MaskType {
}
public void setLineColor(int lineColor) {
this.lineColor = lineColor;
}
public void setMaskColor(int maskColor) {
this.maskColor = maskColor;
}
private int lineColor = Color.WHITE;
private int maskType = MASK_TYPE_ID_CARD_FRONT;
private int maskColor = Color.argb(100, 0, 0, 0);
private Paint eraser = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint pen = new Paint(Paint.ANTI_ALIAS_FLAG);
public Rect frame = new Rect();
public Rect getFrameRect() {
if (maskType == MASK_TYPE_NONE) {
return new Rect(0, 0, getWidth(), getHeight());
} else {
return new Rect(frame);
}
}
public Rect getFrameRectExtend() {
Rect rc = new Rect(frame);
int widthExtend = (int) ((frame.right - frame.left) * 0.02f);
int heightExtend = (int) ((frame.bottom - frame.top) * 0.02f);
rc.left -= widthExtend;
rc.right += widthExtend;
rc.top -= heightExtend;
rc.bottom += heightExtend;
return rc;
}
public void setMaskType(@MaskType int maskType) {
this.maskType = maskType;
switch (maskType) {
case MASK_TYPE_ID_CARD_FRONT:
break;
case MASK_TYPE_NONE:
default:
break;
}
invalidate();
}
public int getMaskType() {
return maskType;
}
public void setOrientation(@CameraView.Orientation int orientation) {
}
public MaskView(Context context) {
super(context);
init();
}
public MaskView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// locatorDrawable = ResourcesCompat.getDrawable(getResources(), R.drawable.id_card_locator_front, null);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0) {
float ratio = h > w ? 0.9f : 0.72f;
int width = (int) (w * ratio);
// int height = width * 400 / 620;
int height = (int) (width * (h - CameraLayout.dpToPx(60)) / w * 1f);
int left = (w - width) / 2;
int top = (h - height) / 2;
int right = width + left;
int bottom = height + top;
frame.left = left;
frame.top = top;
frame.right = right;
frame.bottom = bottom;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect frame = this.frame;
int width = frame.width();
int height = frame.height();
int left = frame.left;
int top = frame.top;
int right = frame.right;
int bottom = frame.bottom;
canvas.drawColor(maskColor);
fillRectRound(left, top, right, bottom, 30, 30, false);
canvas.drawPath(path, pen);
canvas.drawPath(path, eraser);
}
private Path path = new Path();
private Path fillRectRound(float left, float top, float right, float bottom, float rx, float ry, boolean
conformToOriginalPost) {
path.reset();
if (rx < 0) {
rx = 0;
}
if (ry < 0) {
ry = 0;
}
float width = right - left;
float height = bottom - top;
if (rx > width / 2) {
rx = width / 2;
}
if (ry > height / 2) {
ry = height / 2;
}
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
path.rQuadTo(0, -ry, -rx, -ry);
path.rLineTo(-widthMinusCorners, 0);
path.rQuadTo(-rx, 0, -rx, ry);
path.rLineTo(0, heightMinusCorners);
if (conformToOriginalPost) {
path.rLineTo(0, ry);
path.rLineTo(width, 0);
path.rLineTo(0, -ry);
} else {
path.rQuadTo(0, ry, rx, ry);
path.rLineTo(widthMinusCorners, 0);
path.rQuadTo(rx, 0, rx, -ry);
}
path.rLineTo(0, -heightMinusCorners);
path.close();
return path;
}
{
// 硬件加速不支持,图层混合。
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
pen.setColor(Color.WHITE);
pen.setStyle(Paint.Style.STROKE);
pen.setStrokeWidth(6);
eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
private void capture(File file) {
}
}
package com.cordova.camera.view;
public interface PermissionCallback {
boolean onRequestPermission();
}
var exec = require('cordova/exec');
//拍照
exports.takePicture = function(arg, success, error) {
exec(success, error, "camera", "takePicture", [arg]);
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment