diff --git a/README.md b/README.md index 48adde2..f4a832a 100644 --- a/README.md +++ b/README.md @@ -25,43 +25,44 @@ ZXingLite for Android 是ZXing的精简极速版,基于ZXing库优化扫码和 ## ViewfinderView属性说明 -| 属性 | 属性类型 | 默认值 | 属性说明 | -| :------|:----------| :------ | :------ | -| maskColor | color |#60000000| 扫描区外遮罩的颜色 | -| frameColor | color |#7F1FB3E2| 扫描区边框的颜色 | -| cornerColor | color |#FF1FB3E2| 扫描区边角的颜色 | -| laserColor | color |#FF1FB3E2| 扫描区激光线的颜色 | -| labelText | string | | 扫描提示文本信息 | -| labelTextColor | color |#FFC0C0C0| 提示文本字体颜色 | -| labelTextSize | dimension |14sp| 提示文本字体大小 | -| labelTextPadding | dimension |24dp| 提示文本距离扫描区的间距 | -| labelTextWidth | dimension | | 提示文本的宽度,默认为View的宽度 | -| labelTextLocation | enum |bottom| 提示文本显示位置 | -| frameWidth | dimension | | 扫码框宽度 | -| frameHeight | dimension | | 扫码框高度 | -| laserStyle | enum | line | 扫描激光的样式 | -| gridColumn | integer | 20 | 网格扫描激光列数 | -| gridHeight | integer | 40dp | 网格扫描激光高度,为0dp时,表示动态铺满 | -| cornerRectWidth | dimension | 4dp | 扫描区边角的宽 | -| cornerRectHeight | dimension | 16dp | 扫描区边角的高 | +| 属性 | 属性类型 | 默认值 | 属性说明 | +|:------------------------|:----------| :------ | :------ | +| maskColor | color |#60000000| 扫描区外遮罩的颜色 | +| frameColor | color |#7F1FB3E2| 扫描区边框的颜色 | +| cornerColor | color |#FF1FB3E2| 扫描区边角的颜色 | +| laserColor | color |#FF1FB3E2| 扫描区激光线的颜色 | +| labelText | string | | 扫描提示文本信息 | +| labelTextColor | color |#FFC0C0C0| 提示文本字体颜色 | +| labelTextSize | dimension |14sp| 提示文本字体大小 | +| labelTextPadding | dimension |24dp| 提示文本距离扫描区的间距 | +| labelTextWidth | dimension | | 提示文本的宽度,默认为View的宽度 | +| labelTextLocation | enum |bottom| 提示文本显示位置 | +| frameWidth | dimension | | 扫码框宽度 | +| frameHeight | dimension | | 扫码框高度 | +| laserStyle | enum | line | 扫描激光的样式 | +| gridColumn | integer | 20 | 网格扫描激光列数 | +| gridHeight | integer | 40dp | 网格扫描激光高度,为0dp时,表示动态铺满 | +| cornerRectWidth | dimension | 4dp | 扫描区边角的宽 | +| cornerRectHeight | dimension | 16dp | 扫描区边角的高 | | scannerLineMoveDistance | dimension | 2dp | 扫描线每次移动距离 | -| scannerLineHeight | dimension | 5dp | 扫描线高度 | -| frameLineWidth | dimension | 1dp | 边框线宽度 | -| scannerAnimationDelay | integer | 20 | 扫描动画延迟间隔时间,单位:毫秒 | -| frameRatio | float | 0.625f | 扫码框与屏幕占比 | -| framePaddingLeft | dimension | 0 | 扫码框左边的内间距 | -| framePaddingTop | dimension | 0 | 扫码框上边的内间距 | -| framePaddingRight | dimension | 0 | 扫码框右边的内间距 | -| framePaddingBottom | dimension | 0 | 扫码框下边的内间距 | -| frameGravity | enum | center | 扫码框对齐方式 | -| pointColor | color | #FF1FB3E2 | 结果点的颜色 | -| pointStrokeColor | color | #FFFFFFFF | 结果点描边的颜色 | -| pointRadius | dimension | 15dp | 结果点的半径 | -| pointStrokeRatio | float | 1.2 | 结果点描边半径与结果点半径的比例 | -| pointDrawable | reference | | 结果点自定义图片 | -| showPointAnim | boolean | true | 是否显示结果点的动画 | -| laserDrawable | reference | | 扫描激光自定义图片 | -| viewfinderStyle | enum | classic | 取景框样式;支持:classic:经典样式(带扫码框那种)、popular:流行样式(不带扫码框) | +| scannerLineHeight | dimension | 5dp | 扫描线高度 | +| frameLineWidth | dimension | 1dp | 边框线宽度 | +| scannerAnimationDelay | integer | 20 | 扫描动画延迟间隔时间,单位:毫秒 | +| frameRatio | float | 0.625f | 扫码框与屏幕占比 | +| framePaddingLeft | dimension | 0 | 扫码框左边的内间距 | +| framePaddingTop | dimension | 0 | 扫码框上边的内间距 | +| framePaddingRight | dimension | 0 | 扫码框右边的内间距 | +| framePaddingBottom | dimension | 0 | 扫码框下边的内间距 | +| frameGravity | enum | center | 扫码框对齐方式 | +| pointColor | color | #FF1FB3E2 | 结果点的颜色 | +| pointStrokeColor | color | #FFFFFFFF | 结果点描边的颜色 | +| pointRadius | dimension | 15dp | 结果点的半径 | +| pointStrokeRatio | float | 1.2 | 结果点描边半径与结果点半径的比例 | +| pointDrawable | reference | | 结果点自定义图片 | +| showPointAnim | boolean | true | 是否显示结果点的动画 | +| laserDrawable | reference | | 扫描激光自定义图片 | +| laserDrawableRatio | float | 0.625f | 激光扫描图片与屏幕占比 | +| viewfinderStyle | enum | classic | 取景框样式;支持:classic:经典样式(带扫码框那种)、popular:流行样式(不带扫码框) | ## 引入 @@ -83,7 +84,7 @@ allprojects { ```gradle // AndroidX 版本 -implementation 'com.github.jenly1314:zxing-lite:2.3.1' +implementation 'com.github.jenly1314:zxing-lite:2.4.0' ``` @@ -175,7 +176,7 @@ implementation 'com.king.zxing:zxing-lite:1.1.9' // 获取CameraScan,扫码相关的配置设置。CameraScan里面包含部分支持链式调用的方法,即调用返回是CameraScan本身的一些配置建议在startCamera之前调用。 getCameraScan().setPlayBeep(true)//设置是否播放音效,默认为false .setVibrate(true)//设置是否震动,默认为false - .setCameraConfig(new CameraConfig())//设置相机配置信息,CameraConfig可覆写options方法自定义配置 + .setCameraConfig(new ResolutionCameraConfig(this))//设置相机配置信息,CameraConfig可覆写options方法自定义配置 .setNeedAutoZoom(false)//二维码太小时可自动缩放,默认为false .setNeedTouchZoom(true)//支持多指触摸捏合缩放,默认为true .setDarkLightLux(45f)//设置光线足够暗的阈值(单位:lux),需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效 @@ -404,6 +405,12 @@ dependencies { ## 版本记录 +#### v2.4.0:2023-4-15 +* 优化CameraScan的缺省配置(CameraConfig相关配置) +* 优化ViewfinderView自定义属性(新增laserDrawableRatio) +* 优化ImageAnalyzer中YUV数据的处理 +* 更新CameraX至v1.2.2 + #### v2.3.1:2023-3-4 * 更新CameraX至v1.2.1 * 更新Gradle至v7.5 diff --git a/app/build.gradle b/app/build.gradle index 6ab1f31..9e7598a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,18 +1,22 @@ -apply plugin: 'com.android.application' -//apply plugin: 'kotlin-android' -//apply plugin: 'kotlin-android-extensions' +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} android { - compileSdkVersion build_versions.compileSdk - buildToolsVersion build_versions.buildTools + namespace 'com.king.zxing.app' + compileSdk build_versions.compileSdk + defaultConfig { applicationId "com.king.zxing.app" - minSdkVersion build_versions.minSdk - targetSdkVersion build_versions.targetSdk + minSdk build_versions.minSdk + targetSdk build_versions.targetSdk versionCode app_version.versionCode versionName app_version.versionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + buildTypes { release { minifyEnabled false @@ -24,22 +28,16 @@ android { abortOnError false } - compileOptions { coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - -// kotlinOptions { -// jvmTarget = '1.8' -// } } dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') testImplementation deps.test.junit - androidTestImplementation deps.test.runner + androidTestImplementation deps.test.android_ext_junit androidTestImplementation deps.test.espresso implementation deps.androidx.design diff --git a/app/release/app-release.apk b/app/release/app-release.apk index 1edd155..c09d027 100644 Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 962452f..51233fe 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 36, - "versionName": "2.3.1", + "versionCode": 37, + "versionName": "2.4.0", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/java/com/king/zxing/app/CustomCaptureActivity.java b/app/src/main/java/com/king/zxing/app/CustomCaptureActivity.java index 9744cce..56e89f9 100644 --- a/app/src/main/java/com/king/zxing/app/CustomCaptureActivity.java +++ b/app/src/main/java/com/king/zxing/app/CustomCaptureActivity.java @@ -77,7 +77,7 @@ public class CustomCaptureActivity extends CaptureActivity { getCameraScan().setPlayBeep(true)//设置是否播放音效,默认为false .setVibrate(true)//设置是否震动,默认为false // .setCameraConfig(new CameraConfig())//设置相机配置信息,CameraConfig可覆写options方法自定义配置 - .setCameraConfig(new ResolutionCameraConfig(this))//设置CameraConfig,可以根据自己的需求去自定义配置 +// .setCameraConfig(new ResolutionCameraConfig(this))//设置CameraConfig,可以根据自己的需求去自定义配置 .setNeedAutoZoom(false)//二维码太小时可自动缩放,默认为false .setNeedTouchZoom(true)//支持多指触摸捏合缩放,默认为true .setDarkLightLux(45f)//设置光线足够暗的阈值(单位:lux),需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效 @@ -117,12 +117,10 @@ public class CustomCaptureActivity extends CaptureActivity { } private void showToast(String text){ - if(toast == null){ - toast = Toast.makeText(this,text,Toast.LENGTH_SHORT); - }else{ - toast.setText(text); - toast.setDuration(Toast.LENGTH_SHORT); + if(toast != null){ + toast.cancel(); } + toast = Toast.makeText(this,text,Toast.LENGTH_SHORT); toast.show(); } diff --git a/app/src/main/java/com/king/zxing/app/MainActivity.java b/app/src/main/java/com/king/zxing/app/MainActivity.java index 6ffdeae..c160660 100644 --- a/app/src/main/java/com/king/zxing/app/MainActivity.java +++ b/app/src/main/java/com/king/zxing/app/MainActivity.java @@ -32,6 +32,8 @@ import com.king.zxing.util.LogUtils; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; @@ -72,6 +74,8 @@ public class MainActivity extends AppCompatActivity implements EasyPermissions.P private Toast toast; + private ExecutorService executor = Executors.newSingleThreadExecutor(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -97,12 +101,10 @@ public class MainActivity extends AppCompatActivity implements EasyPermissions.P } private void showToast(String text){ - if(toast == null){ - toast = Toast.makeText(this,text,Toast.LENGTH_SHORT); - }else{ - toast.setDuration(Toast.LENGTH_SHORT); - toast.setText(text); + if(toast != null){ + toast.cancel(); } + toast = Toast.makeText(this,text,Toast.LENGTH_SHORT); toast.show(); } @@ -165,7 +167,7 @@ public class MainActivity extends AppCompatActivity implements EasyPermissions.P } private void asyncThread(Runnable runnable){ - new Thread(runnable).start(); + executor.execute(runnable); } /** diff --git a/app/src/main/res/drawable-xxhdpi/ic_laser_line.png b/app/src/main/res/drawable-xxhdpi/ic_laser_line.png new file mode 100644 index 0000000..09a75b6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_laser_line.png differ diff --git a/app/src/main/res/layout/custom_activity.xml b/app/src/main/res/layout/custom_activity.xml index abad373..711f3cc 100644 --- a/app/src/main/res/layout/custom_activity.xml +++ b/app/src/main/res/layout/custom_activity.xml @@ -13,7 +13,10 @@ android:id="@+id/viewfinderView" android:layout_width="match_parent" android:layout_height="match_parent" - app:viewfinderStyle="popular"/> + app:viewfinderStyle="popular" + app:laserStyle="image" + app:laserDrawableRatio="0.8" + app:laserDrawable="@drawable/ic_laser_line"/> * 内置了一些{@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) { diff --git a/zxing-lite/src/main/java/com/king/zxing/CaptureActivity.java b/zxing-lite/src/main/java/com/king/zxing/CaptureActivity.java index 99bb782..8cb1230 100644 --- a/zxing-lite/src/main/java/com/king/zxing/CaptureActivity.java +++ b/zxing-lite/src/main/java/com/king/zxing/CaptureActivity.java @@ -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} */ diff --git a/zxing-lite/src/main/java/com/king/zxing/CaptureFragment.java b/zxing-lite/src/main/java/com/king/zxing/CaptureFragment.java index 462fb97..5793c19 100644 --- a/zxing-lite/src/main/java/com/king/zxing/CaptureFragment.java +++ b/zxing-lite/src/main/java/com/king/zxing/CaptureFragment.java @@ -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; } diff --git a/zxing-lite/src/main/java/com/king/zxing/DecodeConfig.java b/zxing-lite/src/main/java/com/king/zxing/DecodeConfig.java index 308d89f..b059277 100644 --- a/zxing-lite/src/main/java/com/king/zxing/DecodeConfig.java +++ b/zxing-lite/src/main/java/com/king/zxing/DecodeConfig.java @@ -95,6 +95,12 @@ public class DecodeConfig { } + /** + * 获取配置的解码支持类型 {@link DecodeHintType} + * + * @return + * @see {@link DecodeFormatManager} + */ public Map getHints() { return hints; } @@ -105,7 +111,7 @@ public class DecodeConfig { * @param hints {@link DecodeFormatManager} *

* 内置的一些解码可参见如下: - * @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}的配置信息控制预览相关配置信息 *

* 即判定区域分析的优先级顺序为:{@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}的配置信息控制预览相关配置信息 *

* 即判定区域分析的优先级顺序为:{@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}的配置信息控制预览相关配置信息 *

* 即判定区域分析的优先级顺序为:{@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; diff --git a/zxing-lite/src/main/java/com/king/zxing/DecodeFormatManager.java b/zxing-lite/src/main/java/com/king/zxing/DecodeFormatManager.java index 0a08c42..10d6e91 100644 --- a/zxing-lite/src/main/java/com/king/zxing/DecodeFormatManager.java +++ b/zxing-lite/src/main/java/com/king/zxing/DecodeFormatManager.java @@ -41,7 +41,6 @@ public final class DecodeFormatManager { * 二维码 */ public static final Map TWO_DIMENSIONAL_HINTS = new EnumMap<>(DecodeHintType.class); - /** * 默认 */ @@ -86,7 +85,7 @@ public final class DecodeFormatManager { } /** - * 二维码 + * 一维码 * 包括如下几种格式: * {@link BarcodeFormat#CODABAR} * {@link BarcodeFormat#CODE_39} diff --git a/zxing-lite/src/main/java/com/king/zxing/DefaultCameraScan.java b/zxing-lite/src/main/java/com/king/zxing/DefaultCameraScan.java index a62777e..c2d6971 100644 --- a/zxing-lite/src/main/java/com/king/zxing/DefaultCameraScan.java +++ b/zxing-lite/src/main/java/com/king/zxing/DefaultCameraScan.java @@ -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 mCameraProviderFuture; + /** + * 相机 + */ private Camera mCamera; - + /** + * 相机配置 + */ private CameraConfig mCameraConfig; + /** + * 分析器 + */ private Analyzer mAnalyzer; - /** * 是否分析 */ private volatile boolean isAnalyze = true; - /** * 是否已经分析出结果 */ private volatile boolean isAnalyzeResult; - + /** + * 闪光灯(手电筒)视图 + */ private View flashlightView; private MutableLiveData 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) { diff --git a/zxing-lite/src/main/java/com/king/zxing/ICamera.java b/zxing-lite/src/main/java/com/king/zxing/ICamera.java index b8543c2..e12e043 100644 --- a/zxing-lite/src/main/java/com/king/zxing/ICamera.java +++ b/zxing-lite/src/main/java/com/king/zxing/ICamera.java @@ -21,9 +21,9 @@ public interface ICamera { void stopCamera(); /** - * 获取{@link Camera} + * 获取 {@link Camera} * - * @return + * @return {@link Camera} */ @Nullable Camera getCamera(); diff --git a/zxing-lite/src/main/java/com/king/zxing/ICameraControl.java b/zxing-lite/src/main/java/com/king/zxing/ICameraControl.java index 2dfa9c8..8f736bb 100644 --- a/zxing-lite/src/main/java/com/king/zxing/ICameraControl.java +++ b/zxing-lite/src/main/java/com/king/zxing/ICameraControl.java @@ -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(); } diff --git a/zxing-lite/src/main/java/com/king/zxing/ViewfinderView.java b/zxing-lite/src/main/java/com/king/zxing/ViewfinderView.java index 4e3af5b..d8f0259 100644 --- a/zxing-lite/src/main/java/com/king/zxing/ViewfinderView.java +++ b/zxing-lite/src/main/java/com/king/zxing/ViewfinderView.java @@ -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 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); } - } /** diff --git a/zxing-lite/src/main/java/com/king/zxing/analyze/BarcodeFormatAnalyzer.java b/zxing-lite/src/main/java/com/king/zxing/analyze/BarcodeFormatAnalyzer.java index 4d9c521..09de404 100644 --- a/zxing-lite/src/main/java/com/king/zxing/analyze/BarcodeFormatAnalyzer.java +++ b/zxing-lite/src/main/java/com/king/zxing/analyze/BarcodeFormatAnalyzer.java @@ -16,13 +16,15 @@ import java.util.Map; import androidx.annotation.Nullable; /** + * 条码分析器 + * * @author Jenly */ public abstract class BarcodeFormatAnalyzer extends AreaRectAnalyzer { private Reader mReader; - public BarcodeFormatAnalyzer(@Nullable Map hints){ + public BarcodeFormatAnalyzer(@Nullable Map 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; diff --git a/zxing-lite/src/main/java/com/king/zxing/analyze/ImageAnalyzer.java b/zxing-lite/src/main/java/com/king/zxing/analyze/ImageAnalyzer.java index 44cc45b..ce6bf27 100644 --- a/zxing-lite/src/main/java/com/king/zxing/analyze/ImageAnalyzer.java +++ b/zxing-lite/src/main/java/com/king/zxing/analyze/ImageAnalyzer.java @@ -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 Jenly */ 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; diff --git a/zxing-lite/src/main/java/com/king/zxing/analyze/QRCodeAnalyzer.java b/zxing-lite/src/main/java/com/king/zxing/analyze/QRCodeAnalyzer.java index fb8d70e..a6c9af5 100644 --- a/zxing-lite/src/main/java/com/king/zxing/analyze/QRCodeAnalyzer.java +++ b/zxing-lite/src/main/java/com/king/zxing/analyze/QRCodeAnalyzer.java @@ -11,15 +11,17 @@ import androidx.annotation.Nullable; /** + * 二维码分析器 + * * @author Jenly */ public class QRCodeAnalyzer extends BarcodeFormatAnalyzer { public QRCodeAnalyzer() { - this((DecodeConfig)null); + this((DecodeConfig) null); } - public QRCodeAnalyzer(@Nullable Map hints){ + public QRCodeAnalyzer(@Nullable Map hints) { this(new DecodeConfig().setHints(hints)); } diff --git a/zxing-lite/src/main/java/com/king/zxing/config/AspectRatioCameraConfig.java b/zxing-lite/src/main/java/com/king/zxing/config/AspectRatioCameraConfig.java index a12030d..3e5b039 100644 --- a/zxing-lite/src/main/java/com/king/zxing/config/AspectRatioCameraConfig.java +++ b/zxing-lite/src/main/java/com/king/zxing/config/AspectRatioCameraConfig.java @@ -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 diff --git a/zxing-lite/src/main/java/com/king/zxing/config/ResolutionCameraConfig.java b/zxing-lite/src/main/java/com/king/zxing/config/ResolutionCameraConfig.java index 5239679..e8b5376 100644 --- a/zxing-lite/src/main/java/com/king/zxing/config/ResolutionCameraConfig.java +++ b/zxing-lite/src/main/java/com/king/zxing/config/ResolutionCameraConfig.java @@ -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 diff --git a/zxing-lite/src/main/java/com/king/zxing/manager/AmbientLightManager.java b/zxing-lite/src/main/java/com/king/zxing/manager/AmbientLightManager.java index 8db276c..c3d94b3 100644 --- a/zxing-lite/src/main/java/com/king/zxing/manager/AmbientLightManager.java +++ b/zxing-lite/src/main/java/com/king/zxing/manager/AmbientLightManager.java @@ -7,6 +7,8 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; /** + * 环境光线管理器:主要通过传感器来监听光线的亮度变化 + * * @author Jenly */ public class AmbientLightManager implements SensorEventListener { diff --git a/zxing-lite/src/main/java/com/king/zxing/manager/BeepManager.java b/zxing-lite/src/main/java/com/king/zxing/manager/BeepManager.java index b9dd88c..e36dbe2 100644 --- a/zxing-lite/src/main/java/com/king/zxing/manager/BeepManager.java +++ b/zxing-lite/src/main/java/com/king/zxing/manager/BeepManager.java @@ -13,6 +13,8 @@ import com.king.zxing.util.LogUtils; import java.io.Closeable; /** + * 蜂鸣音效管理器:主要用于播放蜂鸣提示音和振动效果 + * * @author Jenly */ public final class BeepManager implements MediaPlayer.OnErrorListener, Closeable { diff --git a/zxing-lite/src/main/java/com/king/zxing/util/BitmapUtils.java b/zxing-lite/src/main/java/com/king/zxing/util/BitmapUtils.java new file mode 100644 index 0000000..19f7c77 --- /dev/null +++ b/zxing-lite/src/main/java/com/king/zxing/util/BitmapUtils.java @@ -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 BitmapUtils + */ +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. + * + *

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 + * + *

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. + * + *

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(); + } + } +} diff --git a/zxing-lite/src/main/res/values/attrs.xml b/zxing-lite/src/main/res/values/attrs.xml index 3aedfdb..ad0125c 100644 --- a/zxing-lite/src/main/res/values/attrs.xml +++ b/zxing-lite/src/main/res/values/attrs.xml @@ -48,6 +48,7 @@ +