更新CameraX至v1.2.2
This commit is contained in:
@@ -46,6 +46,15 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
*/
|
||||
public static int LENS_FACING_BACK = CameraSelector.LENS_FACING_BACK;
|
||||
|
||||
/**
|
||||
* 纵横比:4:3
|
||||
*/
|
||||
public static final float ASPECT_RATIO_4_3 = 4.0F / 3.0F;
|
||||
/**
|
||||
* 纵横比:16:9
|
||||
*/
|
||||
public static final float ASPECT_RATIO_16_9 = 16.0F / 9.0F;
|
||||
|
||||
/**
|
||||
* 是否需要支持自动缩放
|
||||
*/
|
||||
@@ -59,7 +68,7 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
/**
|
||||
* 是否需要支持触摸缩放
|
||||
*
|
||||
* @return
|
||||
* @return 返回是否需要支持触摸缩放
|
||||
*/
|
||||
protected boolean isNeedTouchZoom() {
|
||||
return isNeedTouchZoom;
|
||||
@@ -68,8 +77,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
/**
|
||||
* 设置是否需要支持触摸缩放
|
||||
*
|
||||
* @param needTouchZoom
|
||||
* @return
|
||||
* @param needTouchZoom 是否需要支持触摸缩放
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public CameraScan setNeedTouchZoom(boolean needTouchZoom) {
|
||||
isNeedTouchZoom = needTouchZoom;
|
||||
@@ -79,7 +88,7 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
/**
|
||||
* 是否需要支持自动缩放
|
||||
*
|
||||
* @return
|
||||
* @return 是否需要支持自动缩放
|
||||
*/
|
||||
protected boolean isNeedAutoZoom() {
|
||||
return isNeedAutoZoom;
|
||||
@@ -88,8 +97,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
/**
|
||||
* 设置是否需要支持自动缩放
|
||||
*
|
||||
* @param needAutoZoom
|
||||
* @return
|
||||
* @param needAutoZoom 是否需要支持自动缩放
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public CameraScan setNeedAutoZoom(boolean needAutoZoom) {
|
||||
isNeedAutoZoom = needAutoZoom;
|
||||
@@ -99,7 +108,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
/**
|
||||
* 设置相机配置,请在{@link #startCamera()}之前调用
|
||||
*
|
||||
* @param cameraConfig
|
||||
* @param cameraConfig 相机配置
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public abstract CameraScan setCameraConfig(CameraConfig cameraConfig);
|
||||
|
||||
@@ -113,7 +123,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
* 2. 如果只是想拦截扫码结果回调自己处理逻辑,但并不想继续分析图像(即不想连扫),可通过
|
||||
* 调用getCameraScan().setAnalyzeImage(false)来停止分析图像。
|
||||
*
|
||||
* @param analyze
|
||||
* @param analyze 是否分析图像
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public abstract CameraScan setAnalyzeImage(boolean analyze);
|
||||
|
||||
@@ -123,7 +134,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
* <p>
|
||||
* 内置了一些{@link Analyzer}的实现类如下:
|
||||
*
|
||||
* @param analyzer
|
||||
* @param analyzer 分析器
|
||||
* @return {@link CameraScan}
|
||||
* @see {@link MultiFormatAnalyzer}
|
||||
* @see {@link AreaRectAnalyzer}
|
||||
* @see {@link ImageAnalyzer}
|
||||
@@ -133,47 +145,56 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
public abstract CameraScan setAnalyzer(Analyzer analyzer);
|
||||
|
||||
/**
|
||||
* 设置是否震动
|
||||
* 设置是否振动
|
||||
*
|
||||
* @param vibrate
|
||||
* @param vibrate 是否振动
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public abstract CameraScan setVibrate(boolean vibrate);
|
||||
|
||||
/**
|
||||
* 设置是否播放提示音
|
||||
*
|
||||
* @param playBeep
|
||||
* @param playBeep 是否播放蜂鸣提示音
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public abstract CameraScan setPlayBeep(boolean playBeep);
|
||||
|
||||
/**
|
||||
* 设置扫码结果回调
|
||||
*
|
||||
* @param callback
|
||||
* @param callback 扫码结果回调
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public abstract CameraScan setOnScanResultCallback(OnScanResultCallback callback);
|
||||
|
||||
/**
|
||||
* 绑定手电筒,绑定后可根据光线传感器,动态显示或隐藏手电筒
|
||||
*
|
||||
* @param v
|
||||
* @param v 手电筒视图
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public abstract CameraScan bindFlashlightView(@Nullable View v);
|
||||
|
||||
/**
|
||||
* 设置光线足够暗的阈值(单位:lux),需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
|
||||
*
|
||||
* @param lightLux
|
||||
* @param lightLux 光线亮度阈值
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public abstract CameraScan setDarkLightLux(float lightLux);
|
||||
|
||||
/**
|
||||
* 设置光线足够明亮的阈值(单位:lux),需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
|
||||
*
|
||||
* @param lightLux
|
||||
* @param lightLux 光线亮度阈值
|
||||
* @return {@link CameraScan}
|
||||
*/
|
||||
public abstract CameraScan setBrightLightLux(float lightLux);
|
||||
|
||||
/**
|
||||
* 扫描结果回调
|
||||
*/
|
||||
public interface OnScanResultCallback {
|
||||
/**
|
||||
* 扫码结果回调
|
||||
@@ -193,14 +214,13 @@ public abstract class CameraScan implements ICamera, ICameraControl {
|
||||
default void onScanResultFailure() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析扫码结果
|
||||
* 解析扫描结果
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* @param data 需解析的意图数据
|
||||
* @return 返回解析结果
|
||||
*/
|
||||
@Nullable
|
||||
public static String parseScanResult(Intent data) {
|
||||
|
||||
@@ -45,12 +45,25 @@ import androidx.camera.view.PreviewView;
|
||||
*/
|
||||
public class CaptureActivity extends AppCompatActivity implements CameraScan.OnScanResultCallback {
|
||||
|
||||
/**
|
||||
* 相机权限请求代码
|
||||
*/
|
||||
private static final int CAMERA_PERMISSION_REQUEST_CODE = 0X86;
|
||||
|
||||
/**
|
||||
* 预览视图
|
||||
*/
|
||||
protected PreviewView previewView;
|
||||
/**
|
||||
* 取景视图
|
||||
*/
|
||||
protected ViewfinderView viewfinderView;
|
||||
/**
|
||||
* 手电筒视图
|
||||
*/
|
||||
protected View ivFlashlight;
|
||||
|
||||
/**
|
||||
* CameraScan
|
||||
*/
|
||||
private CameraScan mCameraScan;
|
||||
|
||||
@Override
|
||||
@@ -68,11 +81,11 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
|
||||
public void initUI() {
|
||||
previewView = findViewById(getPreviewViewId());
|
||||
int viewfinderViewId = getViewfinderViewId();
|
||||
if (viewfinderViewId != 0) {
|
||||
if (viewfinderViewId != 0 && viewfinderViewId != View.NO_ID) {
|
||||
viewfinderView = findViewById(viewfinderViewId);
|
||||
}
|
||||
int ivFlashlightId = getFlashlightId();
|
||||
if (ivFlashlightId != 0) {
|
||||
if (ivFlashlightId != 0 && ivFlashlightId != View.NO_ID) {
|
||||
ivFlashlight = findViewById(ivFlashlightId);
|
||||
if (ivFlashlight != null) {
|
||||
ivFlashlight.setOnClickListener(v -> onClickFlashlight());
|
||||
@@ -144,8 +157,8 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
|
||||
/**
|
||||
* 请求Camera权限回调结果
|
||||
*
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
* @param permissions 权限
|
||||
* @param grantResults 授权结果
|
||||
*/
|
||||
public void requestCameraPermissionResult(@NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (PermissionUtils.requestPermissionsResult(Manifest.permission.CAMERA, permissions, grantResults)) {
|
||||
@@ -173,7 +186,7 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
|
||||
/**
|
||||
* 布局ID;通过覆写此方法可以自定义布局
|
||||
*
|
||||
* @return
|
||||
* @return 布局ID
|
||||
*/
|
||||
public int getLayoutId() {
|
||||
return R.layout.zxl_capture;
|
||||
@@ -188,11 +201,10 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
|
||||
return R.id.viewfinderView;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 预览界面{@link #previewView} 的ID
|
||||
* 预览界面{@link #previewView} 的ID;可通过覆写此方法自定义ID
|
||||
*
|
||||
* @return
|
||||
* @return 默认返回{@code R.id.previewView}
|
||||
*/
|
||||
public int getPreviewViewId() {
|
||||
return R.id.previewView;
|
||||
@@ -208,7 +220,7 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link CameraScan}
|
||||
* 获取 {@link CameraScan}
|
||||
*
|
||||
* @return {@link #mCameraScan}
|
||||
*/
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.king.zxing.util.LogUtils;
|
||||
import com.king.zxing.util.PermissionUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.camera.view.PreviewView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
@@ -46,14 +47,29 @@ import androidx.fragment.app.Fragment;
|
||||
*/
|
||||
public class CaptureFragment extends Fragment implements CameraScan.OnScanResultCallback {
|
||||
|
||||
/**
|
||||
* 相机权限请求代码
|
||||
*/
|
||||
private static final int CAMERA_PERMISSION_REQUEST_CODE = 0X86;
|
||||
|
||||
/**
|
||||
* 根视图
|
||||
*/
|
||||
private View mRootView;
|
||||
|
||||
/**
|
||||
* 预览视图
|
||||
*/
|
||||
protected PreviewView previewView;
|
||||
/**
|
||||
* 取景视图
|
||||
*/
|
||||
protected ViewfinderView viewfinderView;
|
||||
/**
|
||||
* 手电筒视图
|
||||
*/
|
||||
protected View ivFlashlight;
|
||||
|
||||
/**
|
||||
* CameraScan
|
||||
*/
|
||||
private CameraScan mCameraScan;
|
||||
|
||||
public static CaptureFragment newInstance() {
|
||||
@@ -70,21 +86,26 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
|
||||
if (isContentView()) {
|
||||
mRootView = createRootView(inflater, container);
|
||||
}
|
||||
initUI();
|
||||
return mRootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
initUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public void initUI() {
|
||||
previewView = mRootView.findViewById(getPreviewViewId());
|
||||
int viewfinderViewId = getViewfinderViewId();
|
||||
if (viewfinderViewId != 0) {
|
||||
if (viewfinderViewId != 0 && viewfinderViewId != View.NO_ID) {
|
||||
viewfinderView = mRootView.findViewById(viewfinderViewId);
|
||||
}
|
||||
int ivFlashlightId = getFlashlightId();
|
||||
if (ivFlashlightId != 0) {
|
||||
if (ivFlashlightId != 0 && ivFlashlightId != View.NO_ID) {
|
||||
ivFlashlight = mRootView.findViewById(ivFlashlightId);
|
||||
if (ivFlashlight != null) {
|
||||
ivFlashlight.setOnClickListener(v -> onClickFlashlight());
|
||||
@@ -156,8 +177,8 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
|
||||
/**
|
||||
* 请求Camera权限回调结果
|
||||
*
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
* @param permissions 权限
|
||||
* @param grantResults 授权结果
|
||||
*/
|
||||
public void requestCameraPermissionResult(@NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (PermissionUtils.requestPermissionsResult(Manifest.permission.CAMERA, permissions, grantResults)) {
|
||||
@@ -168,9 +189,9 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
public void onDestroyView() {
|
||||
releaseCamera();
|
||||
super.onDestroy();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,7 +218,7 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
|
||||
/**
|
||||
* 布局ID;通过覆写此方法可以自定义布局
|
||||
*
|
||||
* @return
|
||||
* @return 布局ID
|
||||
*/
|
||||
public int getLayoutId() {
|
||||
return R.layout.zxl_capture;
|
||||
@@ -213,9 +234,9 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览界面{@link #previewView} 的ID
|
||||
* 预览界面{@link #previewView} 的ID;可通过覆写此方法自定义ID
|
||||
*
|
||||
* @return
|
||||
* @return 默认返回{@code R.id.previewView}
|
||||
*/
|
||||
public int getPreviewViewId() {
|
||||
return R.id.previewView;
|
||||
@@ -231,7 +252,7 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link CameraScan}
|
||||
* 获取 {@link CameraScan}
|
||||
*
|
||||
* @return {@link #mCameraScan}
|
||||
*/
|
||||
@@ -252,6 +273,11 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
|
||||
|
||||
//--------------------------------------------
|
||||
|
||||
/**
|
||||
* 获取根视图
|
||||
*
|
||||
* @return {@link #mRootView}
|
||||
*/
|
||||
public View getRootView() {
|
||||
return mRootView;
|
||||
}
|
||||
|
||||
@@ -95,6 +95,12 @@ public class DecodeConfig {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置的解码支持类型 {@link DecodeHintType}
|
||||
*
|
||||
* @return
|
||||
* @see {@link DecodeFormatManager}
|
||||
*/
|
||||
public Map<DecodeHintType, Object> getHints() {
|
||||
return hints;
|
||||
}
|
||||
@@ -105,7 +111,7 @@ public class DecodeConfig {
|
||||
* @param hints {@link DecodeFormatManager}
|
||||
* <p>
|
||||
* 内置的一些解码可参见如下:
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
* @see {@link DecodeFormatManager#DEFAULT_HINTS}
|
||||
* @see {@link DecodeFormatManager#ALL_HINTS}
|
||||
* @see {@link DecodeFormatManager#CODE_128_HINTS}
|
||||
@@ -124,7 +130,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 是否支持识别反色码,黑白颜色反转
|
||||
*
|
||||
* @return
|
||||
* @return 是否支持识别反色码
|
||||
*/
|
||||
public boolean isSupportLuminanceInvert() {
|
||||
return isSupportLuminanceInvert;
|
||||
@@ -134,7 +140,7 @@ public class DecodeConfig {
|
||||
* 设置是否支持识别反色码,黑白颜色反转
|
||||
*
|
||||
* @param supportLuminanceInvert 默认为{@code false},想要增强支持扫码识别反色码时可使用,相应的也会增加性能消耗。
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
*/
|
||||
public DecodeConfig setSupportLuminanceInvert(boolean supportLuminanceInvert) {
|
||||
isSupportLuminanceInvert = supportLuminanceInvert;
|
||||
@@ -144,7 +150,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 是否支持扫垂直的条码
|
||||
*
|
||||
* @return
|
||||
* @return 是否支持扫垂直的条码
|
||||
*/
|
||||
public boolean isSupportVerticalCode() {
|
||||
return isSupportVerticalCode;
|
||||
@@ -154,7 +160,7 @@ public class DecodeConfig {
|
||||
* 设置是否支持扫垂直的条码
|
||||
*
|
||||
* @param supportVerticalCode 默认为{@code false},想要增强支持扫码识别垂直的条码时可使用,相应的也会增加性能消耗。
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
*/
|
||||
public DecodeConfig setSupportVerticalCode(boolean supportVerticalCode) {
|
||||
isSupportVerticalCode = supportVerticalCode;
|
||||
@@ -164,7 +170,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 是否支持使用多解码
|
||||
*
|
||||
* @return
|
||||
* @return 是否支持使用多解码
|
||||
*/
|
||||
public boolean isMultiDecode() {
|
||||
return isMultiDecode;
|
||||
@@ -185,7 +191,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 是否支持识别反色码(条码黑白颜色反转的码)使用多解码
|
||||
*
|
||||
* @return
|
||||
* @return 是否支持识别反色码
|
||||
*/
|
||||
public boolean isSupportLuminanceInvertMultiDecode() {
|
||||
return isSupportLuminanceInvertMultiDecode;
|
||||
@@ -195,7 +201,7 @@ public class DecodeConfig {
|
||||
* 设置是否支持识别反色码(条码黑白颜色反转的码)使用多解码
|
||||
*
|
||||
* @param supportLuminanceInvertMultiDecode 默认为{@code false},想要增强支持扫码识别反色码时可使用,相应的也会增加性能消耗。
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
* @see {@link HybridBinarizer} , {@link GlobalHistogramBinarizer}
|
||||
*/
|
||||
public DecodeConfig setSupportLuminanceInvertMultiDecode(boolean supportLuminanceInvertMultiDecode) {
|
||||
@@ -206,7 +212,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 是否支持垂直的条码,使用多解码
|
||||
*
|
||||
* @return
|
||||
* @return 是否支持垂直的条码,使用多解码
|
||||
*/
|
||||
public boolean isSupportVerticalCodeMultiDecode() {
|
||||
return isSupportVerticalCodeMultiDecode;
|
||||
@@ -216,7 +222,7 @@ public class DecodeConfig {
|
||||
* 设置是否支持垂直的条码,使用多解码;解码时,对应的二值化的实现: {@link HybridBinarizer} , {@link GlobalHistogramBinarizer}
|
||||
*
|
||||
* @param supportVerticalCodeMultiDecode 默认为{@code false},想要增强支持扫码识别垂直的条码时可使用,相应的也会增加性能消耗。
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
*/
|
||||
public DecodeConfig setSupportVerticalCodeMultiDecode(boolean supportVerticalCodeMultiDecode) {
|
||||
isSupportVerticalCodeMultiDecode = supportVerticalCodeMultiDecode;
|
||||
@@ -226,7 +232,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 需要分析识别区域
|
||||
*
|
||||
* @return
|
||||
* @return 分析识别区域
|
||||
*/
|
||||
public Rect getAnalyzeAreaRect() {
|
||||
return analyzeAreaRect;
|
||||
@@ -244,7 +250,7 @@ public class DecodeConfig {
|
||||
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
|
||||
* <p>
|
||||
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
*/
|
||||
public DecodeConfig setAnalyzeAreaRect(Rect analyzeAreaRect) {
|
||||
this.analyzeAreaRect = analyzeAreaRect;
|
||||
@@ -254,7 +260,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 是否支持全区域扫码识别
|
||||
*
|
||||
* @return
|
||||
* @return 是否支持全区域扫码识别
|
||||
*/
|
||||
public boolean isFullAreaScan() {
|
||||
return isFullAreaScan;
|
||||
@@ -274,7 +280,7 @@ public class DecodeConfig {
|
||||
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
|
||||
* <p>
|
||||
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
*/
|
||||
public DecodeConfig setFullAreaScan(boolean fullAreaScan) {
|
||||
isFullAreaScan = fullAreaScan;
|
||||
@@ -284,7 +290,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
|
||||
*
|
||||
* @return
|
||||
* @return 识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO}
|
||||
*/
|
||||
public float getAreaRectRatio() {
|
||||
return areaRectRatio;
|
||||
@@ -302,7 +308,7 @@ public class DecodeConfig {
|
||||
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
|
||||
* <p>
|
||||
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
*/
|
||||
public DecodeConfig setAreaRectRatio(@FloatRange(from = 0.5, to = 1.0) float areaRectRatio) {
|
||||
this.areaRectRatio = areaRectRatio;
|
||||
@@ -312,7 +318,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 识别区域垂直方向偏移量,支持负数,大于0时,居中心向下偏移,小于0时,居中心向上偏移
|
||||
*
|
||||
* @return
|
||||
* @return 识别区域垂直方向偏移量
|
||||
*/
|
||||
public int getAreaRectVerticalOffset() {
|
||||
return areaRectVerticalOffset;
|
||||
@@ -322,7 +328,7 @@ public class DecodeConfig {
|
||||
* 设置识别区域垂直方向偏移量,支持负数,大于0时,居中心向下偏移,小于0时,居中心向上偏移
|
||||
*
|
||||
* @param areaRectVerticalOffset
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
*/
|
||||
public DecodeConfig setAreaRectVerticalOffset(int areaRectVerticalOffset) {
|
||||
this.areaRectVerticalOffset = areaRectVerticalOffset;
|
||||
@@ -332,7 +338,7 @@ public class DecodeConfig {
|
||||
/**
|
||||
* 识别区域水平方向偏移量,支持负数,大于0时,居中心向右偏移,小于0时,居中心向左偏移
|
||||
*
|
||||
* @return
|
||||
* @return 识别区域水平方向偏移量
|
||||
*/
|
||||
public int getAreaRectHorizontalOffset() {
|
||||
return areaRectHorizontalOffset;
|
||||
@@ -342,7 +348,7 @@ public class DecodeConfig {
|
||||
* 设置识别区域水平方向偏移量,支持负数,大于0时,居中心向右偏移,小于0时,居中心向左偏移
|
||||
*
|
||||
* @param areaRectHorizontalOffset
|
||||
* @return
|
||||
* @return {@link DecodeConfig}
|
||||
*/
|
||||
public DecodeConfig setAreaRectHorizontalOffset(int areaRectHorizontalOffset) {
|
||||
this.areaRectHorizontalOffset = areaRectHorizontalOffset;
|
||||
|
||||
@@ -41,7 +41,6 @@ public final class DecodeFormatManager {
|
||||
* 二维码
|
||||
*/
|
||||
public static final Map<DecodeHintType, Object> TWO_DIMENSIONAL_HINTS = new EnumMap<>(DecodeHintType.class);
|
||||
|
||||
/**
|
||||
* 默认
|
||||
*/
|
||||
@@ -86,7 +85,7 @@ public final class DecodeFormatManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 二维码
|
||||
* 一维码
|
||||
* 包括如下几种格式:
|
||||
* {@link BarcodeFormat#CODABAR}
|
||||
* {@link BarcodeFormat#CODE_39}
|
||||
|
||||
@@ -17,7 +17,9 @@ import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.detector.MathUtils;
|
||||
import com.king.zxing.analyze.Analyzer;
|
||||
import com.king.zxing.analyze.MultiFormatAnalyzer;
|
||||
import com.king.zxing.config.AspectRatioCameraConfig;
|
||||
import com.king.zxing.config.CameraConfig;
|
||||
import com.king.zxing.config.ResolutionCameraConfig;
|
||||
import com.king.zxing.manager.AmbientLightManager;
|
||||
import com.king.zxing.manager.BeepManager;
|
||||
import com.king.zxing.util.LogUtils;
|
||||
@@ -67,14 +69,12 @@ public class DefaultCameraScan extends CameraScan {
|
||||
* opposed to a hover movement gesture.
|
||||
*/
|
||||
private static final int HOVER_TAP_TIMEOUT = 150;
|
||||
|
||||
/**
|
||||
* Defines the maximum distance in pixels that a touch pad touch can move
|
||||
* before being released for it to be considered a tap (click) as opposed
|
||||
* to a hover movement gesture.
|
||||
*/
|
||||
private static final int HOVER_TAP_SLOP = 20;
|
||||
|
||||
/**
|
||||
* 每次缩放改变的步长
|
||||
*/
|
||||
@@ -83,40 +83,73 @@ public class DefaultCameraScan extends CameraScan {
|
||||
private FragmentActivity mFragmentActivity;
|
||||
private Context mContext;
|
||||
private LifecycleOwner mLifecycleOwner;
|
||||
/**
|
||||
* 预览视图
|
||||
*/
|
||||
private PreviewView mPreviewView;
|
||||
|
||||
private ListenableFuture<ProcessCameraProvider> mCameraProviderFuture;
|
||||
/**
|
||||
* 相机
|
||||
*/
|
||||
private Camera mCamera;
|
||||
|
||||
/**
|
||||
* 相机配置
|
||||
*/
|
||||
private CameraConfig mCameraConfig;
|
||||
/**
|
||||
* 分析器
|
||||
*/
|
||||
private Analyzer mAnalyzer;
|
||||
|
||||
/**
|
||||
* 是否分析
|
||||
*/
|
||||
private volatile boolean isAnalyze = true;
|
||||
|
||||
/**
|
||||
* 是否已经分析出结果
|
||||
*/
|
||||
private volatile boolean isAnalyzeResult;
|
||||
|
||||
/**
|
||||
* 闪光灯(手电筒)视图
|
||||
*/
|
||||
private View flashlightView;
|
||||
|
||||
private MutableLiveData<Result> mResultLiveData;
|
||||
|
||||
/**
|
||||
* 扫描结果回调
|
||||
*/
|
||||
private OnScanResultCallback mOnScanResultCallback;
|
||||
|
||||
/**
|
||||
* 蜂鸣音效管理器:主要用于播放蜂鸣提示音和振动效果
|
||||
*/
|
||||
private BeepManager mBeepManager;
|
||||
/**
|
||||
* 环境光线管理器:主要通过传感器来监听光线的亮度变化
|
||||
*/
|
||||
private AmbientLightManager mAmbientLightManager;
|
||||
|
||||
private int mOrientation;
|
||||
private int mImageWidth;
|
||||
private int mImageHeight;
|
||||
/**
|
||||
* 最后自动缩放时间
|
||||
*/
|
||||
private long mLastAutoZoomTime;
|
||||
/**
|
||||
* 最后点击时间,根据两次点击时间间隔用于区分单机和触摸缩放事件
|
||||
*/
|
||||
private long mLastHoveTapTime;
|
||||
/**
|
||||
* 是否是点击事件
|
||||
*/
|
||||
private boolean isClickTap;
|
||||
/**
|
||||
* 按下时X坐标
|
||||
*/
|
||||
private float mDownX;
|
||||
/**
|
||||
* 按下时Y坐标
|
||||
*/
|
||||
private float mDownY;
|
||||
|
||||
public DefaultCameraScan(@NonNull FragmentActivity activity, @NonNull PreviewView previewView) {
|
||||
@@ -199,7 +232,7 @@ public class DefaultCameraScan extends CameraScan {
|
||||
/**
|
||||
* 处理预览视图点击事件;如果触发的点击事件被判定对焦操作,则开始自动对焦
|
||||
*
|
||||
* @param event
|
||||
* @param event 事件
|
||||
*/
|
||||
private void handlePreviewViewClickTap(MotionEvent event) {
|
||||
if (event.getPointerCount() == 1) {
|
||||
@@ -226,8 +259,8 @@ public class DefaultCameraScan extends CameraScan {
|
||||
/**
|
||||
* 开始对焦和测光
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param x X轴坐标
|
||||
* @param y Y轴坐标
|
||||
*/
|
||||
private void startFocusAndMetering(float x, float y) {
|
||||
if (mCamera != null) {
|
||||
@@ -240,19 +273,6 @@ public class DefaultCameraScan extends CameraScan {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化配置
|
||||
*/
|
||||
private void initConfig() {
|
||||
if (mCameraConfig == null) {
|
||||
mCameraConfig = new CameraConfig();
|
||||
}
|
||||
if (mAnalyzer == null) {
|
||||
mAnalyzer = new MultiFormatAnalyzer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CameraScan setCameraConfig(CameraConfig cameraConfig) {
|
||||
if (cameraConfig != null) {
|
||||
@@ -261,12 +281,35 @@ public class DefaultCameraScan extends CameraScan {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化相机配置
|
||||
*/
|
||||
/**
|
||||
* 初始化相机配置
|
||||
*/
|
||||
private void initCameraConfig(Context context) {
|
||||
if (mCameraConfig == null) {
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
int size = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels);
|
||||
// 根据分辨率初始化缺省配置CameraConfig;在此前提下尽可能的找到比屏幕分辨率小一级的配置;在适配、性能与体验之间得有所取舍,找到平衡点。
|
||||
if (size > ResolutionCameraConfig.IMAGE_QUALITY_1080P) {
|
||||
mCameraConfig = new ResolutionCameraConfig(context);
|
||||
} else if (size > ResolutionCameraConfig.IMAGE_QUALITY_720P) {
|
||||
mCameraConfig = new ResolutionCameraConfig(context, ResolutionCameraConfig.IMAGE_QUALITY_720P);
|
||||
} else {
|
||||
mCameraConfig = new AspectRatioCameraConfig(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCamera() {
|
||||
initConfig();
|
||||
initCameraConfig(mContext);
|
||||
if (mAnalyzer == null) {
|
||||
mAnalyzer = new MultiFormatAnalyzer();
|
||||
}
|
||||
mCameraProviderFuture = ProcessCameraProvider.getInstance(mContext);
|
||||
mCameraProviderFuture.addListener(() -> {
|
||||
|
||||
try {
|
||||
Preview preview = mCameraConfig.options(new Preview.Builder());
|
||||
|
||||
@@ -336,6 +379,7 @@ public class DefaultCameraScan extends CameraScan {
|
||||
|
||||
/**
|
||||
* 处理自动缩放
|
||||
*
|
||||
* @param distance
|
||||
* @param result
|
||||
* @return
|
||||
@@ -353,7 +397,8 @@ public class DefaultCameraScan extends CameraScan {
|
||||
|
||||
/**
|
||||
* 扫描结果回调
|
||||
* @param result
|
||||
*
|
||||
* @param result 扫描结果
|
||||
*/
|
||||
private void scanResultCallback(Result result) {
|
||||
if (mOnScanResultCallback != null && mOnScanResultCallback.onScanResultCallback(result)) {
|
||||
@@ -473,11 +518,6 @@ public class DefaultCameraScan extends CameraScan {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持闪光灯
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean hasFlashUnit() {
|
||||
if (mCamera != null) {
|
||||
|
||||
@@ -21,9 +21,9 @@ public interface ICamera {
|
||||
void stopCamera();
|
||||
|
||||
/**
|
||||
* 获取{@link Camera}
|
||||
* 获取 {@link Camera}
|
||||
*
|
||||
* @return
|
||||
* @return {@link Camera}
|
||||
*/
|
||||
@Nullable
|
||||
Camera getCamera();
|
||||
|
||||
@@ -22,7 +22,7 @@ public interface ICameraControl {
|
||||
/**
|
||||
* 缩放到指定比例
|
||||
*
|
||||
* @param ratio
|
||||
* @param ratio 缩放比例
|
||||
*/
|
||||
void zoomTo(float ratio);
|
||||
|
||||
@@ -39,28 +39,28 @@ public interface ICameraControl {
|
||||
/**
|
||||
* 线性缩放到指定比例
|
||||
*
|
||||
* @param linearZoom
|
||||
* @param linearZoom 线性缩放比例;范围在:0.0 ~ 1.0之间
|
||||
*/
|
||||
void lineZoomTo(@FloatRange(from = 0.0, to = 1.0) float linearZoom);
|
||||
|
||||
/**
|
||||
* 设置闪光灯(手电筒)是否开启
|
||||
*
|
||||
* @param torch
|
||||
* @param torch 是否开启闪光灯(手电筒)
|
||||
*/
|
||||
void enableTorch(boolean torch);
|
||||
|
||||
/**
|
||||
* 闪光灯(手电筒)是否开启
|
||||
*
|
||||
* @return
|
||||
* @return 闪光灯(手电筒)是否开启
|
||||
*/
|
||||
boolean isTorchEnabled();
|
||||
|
||||
/**
|
||||
* 是否支持闪光灯
|
||||
*
|
||||
* @return
|
||||
* @return 是否支持闪光灯
|
||||
*/
|
||||
boolean hasFlashUnit();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
@@ -26,8 +27,6 @@ import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.king.zxing.util.LogUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.List;
|
||||
@@ -216,6 +215,10 @@ public class ViewfinderView extends View {
|
||||
|
||||
private Bitmap laserBitmap;
|
||||
|
||||
private float laserBitmapRatio;
|
||||
|
||||
private float laserBitmapWidth;
|
||||
|
||||
private int viewfinderStyle = ViewfinderStyle.CLASSIC;
|
||||
|
||||
private List<Point> pointList;
|
||||
@@ -248,7 +251,7 @@ public class ViewfinderView extends View {
|
||||
*/
|
||||
public enum LaserStyle {
|
||||
NONE(0), LINE(1), GRID(2), IMAGE(3);
|
||||
private int mValue;
|
||||
private final int mValue;
|
||||
|
||||
LaserStyle(int value) {
|
||||
mValue = value;
|
||||
@@ -270,7 +273,7 @@ public class ViewfinderView extends View {
|
||||
public enum TextLocation {
|
||||
TOP(0), BOTTOM(1);
|
||||
|
||||
private int mValue;
|
||||
private final int mValue;
|
||||
|
||||
TextLocation(int value) {
|
||||
mValue = value;
|
||||
@@ -292,7 +295,7 @@ public class ViewfinderView extends View {
|
||||
public enum FrameGravity {
|
||||
CENTER(0), LEFT(1), TOP(2), RIGHT(3), BOTTOM(4);
|
||||
|
||||
private int mValue;
|
||||
private final int mValue;
|
||||
|
||||
FrameGravity(int value) {
|
||||
mValue = value;
|
||||
@@ -369,6 +372,7 @@ public class ViewfinderView extends View {
|
||||
isShowPointAnim = array.getBoolean(R.styleable.ViewfinderView_showPointAnim, true);
|
||||
Drawable pointDrawable = array.getDrawable(R.styleable.ViewfinderView_pointDrawable);
|
||||
Drawable laserDrawable = array.getDrawable(R.styleable.ViewfinderView_laserDrawable);
|
||||
laserBitmapRatio = array.getFloat(R.styleable.ViewfinderView_laserDrawableRatio, 0.625f);
|
||||
viewfinderStyle = array.getInt(R.styleable.ViewfinderView_viewfinderStyle, ViewfinderStyle.CLASSIC);
|
||||
|
||||
array.recycle();
|
||||
@@ -433,6 +437,24 @@ public class ViewfinderView extends View {
|
||||
this.laserStyle = laserStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置激光扫描自定义图片
|
||||
*
|
||||
* @param drawableResId
|
||||
*/
|
||||
public void setLaserDrawable(@DrawableRes int drawableResId) {
|
||||
setLaserBitmap(BitmapFactory.decodeResource(getResources(), drawableResId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置激光扫描自定义图片
|
||||
*
|
||||
* @param laserBitmap
|
||||
*/
|
||||
public void setLaserBitmap(Bitmap laserBitmap) {
|
||||
this.laserBitmap = laserBitmap;
|
||||
scaleLaserBitmap();
|
||||
}
|
||||
public void setPointImageResource(@DrawableRes int drawable) {
|
||||
setPointBitmap(BitmapFactory.decodeResource(getResources(), drawable));
|
||||
}
|
||||
@@ -448,10 +470,26 @@ public class ViewfinderView extends View {
|
||||
initFrame(getWidth(),getHeight());
|
||||
}
|
||||
|
||||
private void scaleLaserBitmap() {
|
||||
if (laserBitmap != null && laserBitmapWidth > 0) {
|
||||
float ratio = laserBitmapWidth / laserBitmap.getWidth();
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postScale(ratio, ratio);
|
||||
int w = laserBitmap.getWidth();
|
||||
int h = laserBitmap.getHeight();
|
||||
laserBitmap = Bitmap.createBitmap(laserBitmap, 0, 0, w, h, matrix, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void initFrame(int width, int height) {
|
||||
|
||||
int size = (int) (Math.min(width, height) * frameRatio);
|
||||
|
||||
if (laserBitmapWidth <= 0) {
|
||||
laserBitmapWidth = Math.min(width, height) * laserBitmapRatio;
|
||||
scaleLaserBitmap();
|
||||
}
|
||||
|
||||
if (frameWidth <= 0 || frameWidth > width) {
|
||||
frameWidth = size;
|
||||
}
|
||||
@@ -582,7 +620,7 @@ public class ViewfinderView extends View {
|
||||
private void drawImageScanner(Canvas canvas, Rect frame) {
|
||||
if (laserBitmap != null) {
|
||||
paint.setColor(Color.WHITE);
|
||||
canvas.drawBitmap(laserBitmap, frame.left, scannerStart, paint);
|
||||
canvas.drawBitmap(laserBitmap, (getWidth() - laserBitmap.getWidth()) / 2, scannerStart, paint);
|
||||
if (scannerStart < scannerEnd) {
|
||||
scannerStart += scannerLineMoveDistance;
|
||||
} else {
|
||||
@@ -591,7 +629,6 @@ public class ViewfinderView extends View {
|
||||
} else {
|
||||
drawLineScanner(canvas, frame);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,13 +16,15 @@ import java.util.Map;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 条码分析器
|
||||
*
|
||||
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
|
||||
*/
|
||||
public abstract class BarcodeFormatAnalyzer extends AreaRectAnalyzer {
|
||||
|
||||
private Reader mReader;
|
||||
|
||||
public BarcodeFormatAnalyzer(@Nullable Map<DecodeHintType,Object> hints){
|
||||
public BarcodeFormatAnalyzer(@Nullable Map<DecodeHintType, Object> hints) {
|
||||
this(new DecodeConfig().setHints(hints));
|
||||
}
|
||||
|
||||
@@ -31,62 +33,62 @@ public abstract class BarcodeFormatAnalyzer extends AreaRectAnalyzer {
|
||||
initReader();
|
||||
}
|
||||
|
||||
private void initReader(){
|
||||
private void initReader() {
|
||||
mReader = createReader();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Result analyze(byte[] data, int dataWidth, int dataHeight,int left,int top,int width,int height) {
|
||||
public Result analyze(byte[] data, int dataWidth, int dataHeight, int left, int top, int width, int height) {
|
||||
Result rawResult = null;
|
||||
if(mReader != null){
|
||||
if (mReader != null) {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data,dataWidth,dataHeight,left,top,width,height,false);
|
||||
rawResult = decodeInternal(source,isMultiDecode);
|
||||
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data, dataWidth, dataHeight, left, top, width, height, false);
|
||||
rawResult = decodeInternal(source, isMultiDecode);
|
||||
|
||||
if(rawResult == null && mDecodeConfig != null){
|
||||
if(rawResult == null && mDecodeConfig.isSupportVerticalCode()){
|
||||
if (rawResult == null && mDecodeConfig != null) {
|
||||
if (rawResult == null && mDecodeConfig.isSupportVerticalCode()) {
|
||||
byte[] rotatedData = new byte[data.length];
|
||||
for (int y = 0; y < dataHeight; y++) {
|
||||
for (int x = 0; x < dataWidth; x++){
|
||||
for (int x = 0; x < dataWidth; x++) {
|
||||
rotatedData[x * dataHeight + dataHeight - y - 1] = data[x + y * dataWidth];
|
||||
}
|
||||
}
|
||||
rawResult = decodeInternal(new PlanarYUVLuminanceSource(rotatedData,dataHeight,dataWidth,top,left,height,width,false),mDecodeConfig.isSupportVerticalCodeMultiDecode());
|
||||
rawResult = decodeInternal(new PlanarYUVLuminanceSource(rotatedData, dataHeight, dataWidth, top, left, height, width, false), mDecodeConfig.isSupportVerticalCodeMultiDecode());
|
||||
}
|
||||
|
||||
if(mDecodeConfig.isSupportLuminanceInvert()){
|
||||
rawResult = decodeInternal(source.invert(),mDecodeConfig.isSupportLuminanceInvertMultiDecode());
|
||||
if (mDecodeConfig.isSupportLuminanceInvert()) {
|
||||
rawResult = decodeInternal(source.invert(), mDecodeConfig.isSupportLuminanceInvertMultiDecode());
|
||||
}
|
||||
}
|
||||
if(rawResult != null){
|
||||
if (rawResult != null) {
|
||||
long end = System.currentTimeMillis();
|
||||
LogUtils.d("Found barcode in " + (end - start) + " ms");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
}finally {
|
||||
} finally {
|
||||
mReader.reset();
|
||||
}
|
||||
}
|
||||
return rawResult;
|
||||
}
|
||||
|
||||
private Result decodeInternal(LuminanceSource source,boolean isMultiDecode){
|
||||
private Result decodeInternal(LuminanceSource source, boolean isMultiDecode) {
|
||||
Result result = null;
|
||||
try{
|
||||
try{
|
||||
try {
|
||||
try {
|
||||
//采用HybridBinarizer解析
|
||||
result = mReader.decode(new BinaryBitmap(new HybridBinarizer(source)),mHints);
|
||||
}catch (Exception e){
|
||||
result = mReader.decode(new BinaryBitmap(new HybridBinarizer(source)), mHints);
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
if(isMultiDecode && result == null){
|
||||
if (isMultiDecode && result == null) {
|
||||
//如果没有解析成功,再采用GlobalHistogramBinarizer解析一次
|
||||
result = mReader.decode(new BinaryBitmap(new GlobalHistogramBinarizer(source)),mHints);
|
||||
result = mReader.decode(new BinaryBitmap(new GlobalHistogramBinarizer(source)), mHints);
|
||||
}
|
||||
}catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -5,22 +5,23 @@ import android.content.res.Configuration;
|
||||
import android.graphics.ImageFormat;
|
||||
|
||||
import com.google.zxing.Result;
|
||||
import com.king.zxing.util.BitmapUtils;
|
||||
import com.king.zxing.util.LogUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.camera.core.ImageProxy;
|
||||
|
||||
/**
|
||||
* 图像分析器
|
||||
*
|
||||
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
|
||||
*/
|
||||
public abstract class ImageAnalyzer implements Analyzer {
|
||||
|
||||
/**
|
||||
* 分析图像数据
|
||||
*
|
||||
* @param data
|
||||
* @param width
|
||||
* @param height
|
||||
@@ -29,25 +30,23 @@ public abstract class ImageAnalyzer implements Analyzer {
|
||||
public abstract Result analyze(byte[] data, int width, int height);
|
||||
|
||||
@Override
|
||||
public Result analyze(@NonNull ImageProxy image,int orientation) {
|
||||
if(image.getFormat() == ImageFormat.YUV_420_888){
|
||||
@SuppressLint("UnsafeExperimentalUsageError")
|
||||
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
|
||||
byte[] data = new byte[buffer.remaining()];
|
||||
buffer.get(data);
|
||||
public Result analyze(@NonNull ImageProxy image, int orientation) {
|
||||
if (image.getFormat() == ImageFormat.YUV_420_888) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
if(orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
byte[] data = BitmapUtils.yuv420ThreePlanesToNV21(image.getImage().getPlanes(), width, height).array();
|
||||
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
byte[] rotatedData = new byte[data.length];
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++){
|
||||
for (int x = 0; x < width; x++) {
|
||||
rotatedData[x * height + height - y - 1] = data[x + y * width];
|
||||
}
|
||||
}
|
||||
return analyze(rotatedData,height,width);
|
||||
return analyze(rotatedData, height, width);
|
||||
}
|
||||
return analyze(data,width,height);
|
||||
}else{
|
||||
return analyze(data, width, height);
|
||||
} else {
|
||||
LogUtils.w("imageFormat: " + image.getFormat());
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -11,15 +11,17 @@ import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* 二维码分析器
|
||||
*
|
||||
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
|
||||
*/
|
||||
public class QRCodeAnalyzer extends BarcodeFormatAnalyzer {
|
||||
|
||||
public QRCodeAnalyzer() {
|
||||
this((DecodeConfig)null);
|
||||
this((DecodeConfig) null);
|
||||
}
|
||||
|
||||
public QRCodeAnalyzer(@Nullable Map<DecodeHintType,Object> hints){
|
||||
public QRCodeAnalyzer(@Nullable Map<DecodeHintType, Object> hints) {
|
||||
this(new DecodeConfig().setHints(hints));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@ package com.king.zxing.config;
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import com.king.zxing.CameraScan;
|
||||
import com.king.zxing.util.LogUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.camera.core.AspectRatio;
|
||||
import androidx.camera.core.CameraSelector;
|
||||
@@ -18,6 +21,9 @@ import androidx.camera.core.Preview;
|
||||
*/
|
||||
public final class AspectRatioCameraConfig extends CameraConfig {
|
||||
|
||||
/**
|
||||
* 纵横比
|
||||
*/
|
||||
private int mAspectRatio;
|
||||
|
||||
public AspectRatioCameraConfig(Context context) {
|
||||
@@ -34,14 +40,15 @@ public final class AspectRatioCameraConfig extends CameraConfig {
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
int width = displayMetrics.widthPixels;
|
||||
int height = displayMetrics.heightPixels;
|
||||
LogUtils.d(String.format(Locale.getDefault(), "displayMetrics: %dx%d", width, height));
|
||||
|
||||
float ratio = Math.max(width, height) / Math.min(width, height);
|
||||
if (Math.abs(ratio - 4.0F / 3.0F) < Math.abs(ratio - 16.0F / 9.0F)) {
|
||||
float ratio = Math.max(width, height) / (float) Math.min(width, height);
|
||||
if (Math.abs(ratio - CameraScan.ASPECT_RATIO_4_3) < Math.abs(ratio - CameraScan.ASPECT_RATIO_16_9)) {
|
||||
mAspectRatio = AspectRatio.RATIO_4_3;
|
||||
} else {
|
||||
mAspectRatio = AspectRatio.RATIO_16_9;
|
||||
}
|
||||
LogUtils.d("aspectRatio:" + mAspectRatio);
|
||||
LogUtils.d("aspectRatio: " + mAspectRatio);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Size;
|
||||
|
||||
import com.king.zxing.CameraScan;
|
||||
import com.king.zxing.util.LogUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
@@ -29,7 +30,9 @@ public class ResolutionCameraConfig extends CameraConfig {
|
||||
*/
|
||||
public static final int IMAGE_QUALITY_720P = 720;
|
||||
|
||||
|
||||
/**
|
||||
* 目标尺寸
|
||||
*/
|
||||
private Size mTargetSize;
|
||||
|
||||
/**
|
||||
@@ -62,29 +65,27 @@ public class ResolutionCameraConfig extends CameraConfig {
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
int width = displayMetrics.widthPixels;
|
||||
int height = displayMetrics.heightPixels;
|
||||
LogUtils.d(String.format(Locale.getDefault(), "displayMetrics: %dx%d", width, height));
|
||||
|
||||
LogUtils.d(String.format(Locale.getDefault(), "displayMetrics:%d x %d", width, height));
|
||||
// 因为为了保持流畅性和性能,尽可能的限制在imageQuality(默认:1080p),在此前提下尽可能的找到屏幕接近的分辨率
|
||||
if (width < height) {
|
||||
float ratio = height / (float) width;
|
||||
int size = Math.min(width, imageQuality);
|
||||
float ratio = width / (float) height;
|
||||
if (ratio > 0.7F) {
|
||||
// 一般应用于平板
|
||||
mTargetSize = new Size(size, (int) (size / 3.0F * 4.0F));
|
||||
if (Math.abs(ratio - CameraScan.ASPECT_RATIO_4_3) < Math.abs(ratio - CameraScan.ASPECT_RATIO_16_9)) {
|
||||
mTargetSize = new Size(size, Math.round(size * CameraScan.ASPECT_RATIO_4_3));
|
||||
} else {
|
||||
mTargetSize = new Size(size, (int) (size / 9.0F * 16.0F));
|
||||
mTargetSize = new Size(size, Math.round(size * CameraScan.ASPECT_RATIO_16_9));
|
||||
}
|
||||
} else {
|
||||
int size = Math.min(height, imageQuality);
|
||||
float ratio = height / (float) width;
|
||||
if (ratio > 0.7F) {
|
||||
// 一般应用于平板
|
||||
mTargetSize = new Size((int) (size / 3.0F * 4.0F), size);
|
||||
float ratio = width / (float) height;
|
||||
if (Math.abs(ratio - CameraScan.ASPECT_RATIO_4_3) < Math.abs(ratio - CameraScan.ASPECT_RATIO_16_9)) {
|
||||
mTargetSize = new Size(Math.round(size * CameraScan.ASPECT_RATIO_4_3), size);
|
||||
} else {
|
||||
mTargetSize = new Size((int) (size / 9.0F * 16.0F), size);
|
||||
mTargetSize = new Size(Math.round(size * CameraScan.ASPECT_RATIO_16_9), size);
|
||||
}
|
||||
}
|
||||
LogUtils.d("targetSize:" + mTargetSize);
|
||||
LogUtils.d("targetSize: " + mTargetSize);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -7,6 +7,8 @@ import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
|
||||
/**
|
||||
* 环境光线管理器:主要通过传感器来监听光线的亮度变化
|
||||
*
|
||||
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
|
||||
*/
|
||||
public class AmbientLightManager implements SensorEventListener {
|
||||
|
||||
@@ -13,6 +13,8 @@ import com.king.zxing.util.LogUtils;
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* 蜂鸣音效管理器:主要用于播放蜂鸣提示音和振动效果
|
||||
*
|
||||
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
|
||||
*/
|
||||
public final class BeepManager implements MediaPlayer.OnErrorListener, Closeable {
|
||||
|
||||
273
zxing-lite/src/main/java/com/king/zxing/util/BitmapUtils.java
Normal file
273
zxing-lite/src/main/java/com/king/zxing/util/BitmapUtils.java
Normal file
@@ -0,0 +1,273 @@
|
||||
package com.king.zxing.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentResolver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.YuvImage;
|
||||
import android.media.ExifInterface;
|
||||
import android.media.Image;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.camera.core.ImageProxy;
|
||||
|
||||
/**
|
||||
* Utils functions for bitmap conversions.
|
||||
*
|
||||
* @see <a href="https://github.com/googlesamples/mlkit/blob/master/android/vision-quickstart/app/src/main/java/com/google/mlkit/vision/demo/BitmapUtils.java">BitmapUtils</a>
|
||||
*/
|
||||
public class BitmapUtils {
|
||||
|
||||
private BitmapUtils() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts NV21 format byte buffer to bitmap.
|
||||
*/
|
||||
@Nullable
|
||||
public static Bitmap getBitmap(ByteBuffer data, int width, int height, int rotationDegrees) {
|
||||
data.rewind();
|
||||
byte[] imageInBuffer = new byte[data.limit()];
|
||||
data.get(imageInBuffer, 0, imageInBuffer.length);
|
||||
try {
|
||||
YuvImage image = new YuvImage(
|
||||
imageInBuffer, ImageFormat.NV21, width, height, null);
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
image.compressToJpeg(new Rect(0, 0, width, height), 80, stream);
|
||||
|
||||
Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
|
||||
|
||||
stream.close();
|
||||
return rotateBitmap(bmp, rotationDegrees, false, false);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e("Error: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a YUV_420_888 image from CameraX API to a bitmap.
|
||||
*/
|
||||
@Nullable
|
||||
public static Bitmap getBitmap(ImageProxy image) {
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
ByteBuffer nv21Buffer = yuv420ThreePlanesToNV21(image.getImage().getPlanes(), image.getWidth(), image.getHeight());
|
||||
return getBitmap(nv21Buffer, image.getWidth(), image.getHeight(), image.getImageInfo().getRotationDegrees());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates a bitmap if it is converted from a bytebuffer.
|
||||
*/
|
||||
private static Bitmap rotateBitmap(
|
||||
Bitmap bitmap, int rotationDegrees, boolean flipX, boolean flipY) {
|
||||
Matrix matrix = new Matrix();
|
||||
|
||||
// Rotate the image back to straight.
|
||||
matrix.postRotate(rotationDegrees);
|
||||
|
||||
// Mirror the image along the X or Y axis.
|
||||
matrix.postScale(flipX ? -1.0f : 1.0f, flipY ? -1.0f : 1.0f);
|
||||
Bitmap rotatedBitmap =
|
||||
Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
|
||||
// Recycle the old bitmap if it has changed.
|
||||
if (rotatedBitmap != bitmap) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
return rotatedBitmap;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@Nullable
|
||||
public static Bitmap getBitmapFromContentUri(ContentResolver contentResolver, Uri imageUri)
|
||||
throws IOException {
|
||||
Bitmap decodedBitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri);
|
||||
if (decodedBitmap == null) {
|
||||
return null;
|
||||
}
|
||||
int orientation = getExifOrientationTag(contentResolver, imageUri);
|
||||
|
||||
int rotationDegrees = 0;
|
||||
boolean flipX = false;
|
||||
boolean flipY = false;
|
||||
// See e.g. https://magnushoff.com/articles/jpeg-orientation/ for a detailed explanation on each
|
||||
// orientation.
|
||||
switch (orientation) {
|
||||
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
|
||||
flipX = true;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
rotationDegrees = 90;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_TRANSPOSE:
|
||||
rotationDegrees = 90;
|
||||
flipX = true;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
rotationDegrees = 180;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
|
||||
flipY = true;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
rotationDegrees = -90;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_TRANSVERSE:
|
||||
rotationDegrees = -90;
|
||||
flipX = true;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_UNDEFINED:
|
||||
case ExifInterface.ORIENTATION_NORMAL:
|
||||
default:
|
||||
// No transformations necessary in this case.
|
||||
}
|
||||
|
||||
return rotateBitmap(decodedBitmap, rotationDegrees, flipX, flipY);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
private static int getExifOrientationTag(ContentResolver resolver, Uri imageUri) {
|
||||
// We only support parsing EXIF orientation tag from local file on the device.
|
||||
// See also:
|
||||
// https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html
|
||||
if (!ContentResolver.SCHEME_CONTENT.equals(imageUri.getScheme())
|
||||
&& !ContentResolver.SCHEME_FILE.equals(imageUri.getScheme())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ExifInterface exif;
|
||||
try (InputStream inputStream = resolver.openInputStream(imageUri)) {
|
||||
if (inputStream == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
exif = new ExifInterface(inputStream);
|
||||
} catch (IOException e) {
|
||||
LogUtils.e("failed to open file to read rotation meta data: " + imageUri, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts YUV_420_888 to NV21 bytebuffer.
|
||||
*
|
||||
* <p>The NV21 format consists of a single byte array containing the Y, U and V values. For an
|
||||
* image of size S, the first S positions of the array contain all the Y values. The remaining
|
||||
* positions contain interleaved V and U values. U and V are subsampled by a factor of 2 in both
|
||||
* dimensions, so there are S/4 U values and S/4 V values. In summary, the NV21 array will contain
|
||||
* S Y values followed by S/4 VU values: YYYYYYYYYYYYYY(...)YVUVUVUVU(...)VU
|
||||
*
|
||||
* <p>YUV_420_888 is a generic format that can describe any YUV image where U and V are subsampled
|
||||
* by a factor of 2 in both dimensions. {@link Image#getPlanes} returns an array with the Y, U and
|
||||
* V planes. The Y plane is guaranteed not to be interleaved, so we can just copy its values into
|
||||
* the first part of the NV21 array. The U and V planes may already have the representation in the
|
||||
* NV21 format. This happens if the planes share the same buffer, the V buffer is one position
|
||||
* before the U buffer and the planes have a pixelStride of 2. If this is case, we can just copy
|
||||
* them to the NV21 array.
|
||||
*/
|
||||
public static ByteBuffer yuv420ThreePlanesToNV21(Image.Plane[] yuv420888planes, int width, int height) {
|
||||
int imageSize = width * height;
|
||||
byte[] out = new byte[imageSize + 2 * (imageSize / 4)];
|
||||
|
||||
if (areUVPlanesNV21(yuv420888planes, width, height)) {
|
||||
// Copy the Y values.
|
||||
yuv420888planes[0].getBuffer().get(out, 0, imageSize);
|
||||
|
||||
ByteBuffer uBuffer = yuv420888planes[1].getBuffer();
|
||||
ByteBuffer vBuffer = yuv420888planes[2].getBuffer();
|
||||
// Get the first V value from the V buffer, since the U buffer does not contain it.
|
||||
vBuffer.get(out, imageSize, 1);
|
||||
// Copy the first U value and the remaining VU values from the U buffer.
|
||||
uBuffer.get(out, imageSize + 1, 2 * imageSize / 4 - 1);
|
||||
} else {
|
||||
// Fallback to copying the UV values one by one, which is slower but also works.
|
||||
// Unpack Y.
|
||||
unpackPlane(yuv420888planes[0], width, height, out, 0, 1);
|
||||
// Unpack U.
|
||||
unpackPlane(yuv420888planes[1], width, height, out, imageSize + 1, 2);
|
||||
// Unpack V.
|
||||
unpackPlane(yuv420888planes[2], width, height, out, imageSize, 2);
|
||||
}
|
||||
|
||||
return ByteBuffer.wrap(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the UV plane buffers of a YUV_420_888 image are in the NV21 format.
|
||||
*/
|
||||
private static boolean areUVPlanesNV21(Image.Plane[] planes, int width, int height) {
|
||||
int imageSize = width * height;
|
||||
|
||||
ByteBuffer uBuffer = planes[1].getBuffer();
|
||||
ByteBuffer vBuffer = planes[2].getBuffer();
|
||||
|
||||
// Backup buffer properties.
|
||||
int vBufferPosition = vBuffer.position();
|
||||
int uBufferLimit = uBuffer.limit();
|
||||
|
||||
// Advance the V buffer by 1 byte, since the U buffer will not contain the first V value.
|
||||
vBuffer.position(vBufferPosition + 1);
|
||||
// Chop off the last byte of the U buffer, since the V buffer will not contain the last U value.
|
||||
uBuffer.limit(uBufferLimit - 1);
|
||||
|
||||
// Check that the buffers are equal and have the expected number of elements.
|
||||
boolean areNV21 =
|
||||
(vBuffer.remaining() == (2 * imageSize / 4 - 2)) && (vBuffer.compareTo(uBuffer) == 0);
|
||||
|
||||
// Restore buffers to their initial state.
|
||||
vBuffer.position(vBufferPosition);
|
||||
uBuffer.limit(uBufferLimit);
|
||||
|
||||
return areNV21;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack an image plane into a byte array.
|
||||
*
|
||||
* <p>The input plane data will be copied in 'out', starting at 'offset' and every pixel will be
|
||||
* spaced by 'pixelStride'. Note that there is no row padding on the output.
|
||||
*/
|
||||
private static void unpackPlane(
|
||||
Image.Plane plane, int width, int height, byte[] out, int offset, int pixelStride) {
|
||||
ByteBuffer buffer = plane.getBuffer();
|
||||
buffer.rewind();
|
||||
|
||||
// Compute the size of the current plane.
|
||||
// We assume that it has the aspect ratio as the original image.
|
||||
int numRow = (buffer.limit() + plane.getRowStride() - 1) / plane.getRowStride();
|
||||
if (numRow == 0) {
|
||||
return;
|
||||
}
|
||||
int scaleFactor = height / numRow;
|
||||
int numCol = width / scaleFactor;
|
||||
|
||||
// Extract the data in the output buffer.
|
||||
int outputPos = offset;
|
||||
int rowStart = 0;
|
||||
for (int row = 0; row < numRow; row++) {
|
||||
int inputPos = rowStart;
|
||||
for (int col = 0; col < numCol; col++) {
|
||||
out[outputPos] = buffer.get(inputPos);
|
||||
outputPos += pixelStride;
|
||||
inputPos += plane.getPixelStride();
|
||||
}
|
||||
rowStart += plane.getRowStride();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user