掃條碼集成Activity(Zbar 解碼、ZXing 管理相機)

一 ,前言

         現在的掃條碼是非常普遍了,連華爲手機在系統照相時,還可以自動toast顯示掃到的二維碼等,特別廣泛。

        這陣子需要搞個掃條碼的功能,主要是條碼,二維碼等。之前用的一直都是 Zxing 的jar包,按照網上的弄。最近客戶反映有些長條碼特別難掃。試用了ZBar 後,發現掃描速度有提升,並且長條碼識別很高。

參考了網絡上的一些ZBar或者zxing應用,總是不如意,有些橫屏,有些是中文亂碼,有些是 ISBN13不能識別。基於此,我在解決了上述問題後,做了一些集成。


以下爲參考鏈接,感謝!

參考鏈接:  http://www.itnose.net/detail/6155831.html (改掃描框)

                    http://blog.csdn.net/skillcollege (zbar zxing) 基本上我的項目就是在這個基礎上弄的,後面參考了github上的一個類似的項目,應該也是這個博主的,只不過這個鏈接的項目,掃ISBN13 條碼有問題,以及沒有橫屏的。爲了方便被別人用,我界面不是用 xml寫的,照着xml的屬性用純java代碼寫。後來發現多此一舉,不過不管了。粗糙地用還是可以,掃描框不滿意,自己替換圖片吧。

                




二, 說明

      我將最終形成的代碼打成jar 包(包含圖片資源),如果對界面要求不高,可以直接用(方法見後面說明),或者替換圖片。如果要定製就只能修改 initUI 了(有兩種方法)。

      此項目的特點:  1. 界面基本固定,可以替換圖片,更改掃描框樣式和掃描線

                                 2. 支持橫屏和豎屏(全屏) ,識別區域與相機預覽區域相匹配

                                 3. zxing 管理相機,zbar 解碼。二合一,超強解碼。

                                 4. 界面上方有 取消按鈕 和  閃光燈(後期有時間考慮增加選擇圖庫中的圖片來解碼,也可能沒時間)


接下來的第三第四步是爲了後面的集成工程服務的。

三、Zbar工程NDK編譯

       這一步確實麻煩,可以找網上的方法。 我這裏直接給zbar的工程,有NDK的可以試試(導入後添加本地支持就可以編譯了)。不過覺得沒必要。底層一般都沒有變。可以直接應用它的so庫和jar包,這個已經修復中文亂碼的了。等會會說明一下幾個zbar 的方法

       zbar jni 工程下載地址:  NDK工程 (也是下載 csdn 的博友的,忘記是誰的了。)  

       生成的 so 文件 和 jar包 : zbar so 文件 

四、ZXing 核心庫簡化

      已經簡化的ZXing 的代碼(java 1.6版本),地址:zxing java 核心庫  

      順便說一句,之前直接用了網上的簡化zxing.jar,但它是 java 1.7編譯的,有些1.7 的API  在1.6沒有。所以直接給別人用(有可能他用的是jdk 1.6),會發生某個類沒找到的異常

       jar 包下載地址:zxing.jar


好了,前面弄完了,開始我們的主要內容了。

五、集成工程(主要工作是說明一些疑惑的地方)

1. 創建一個工程,我幫你們都弄好了。直接下吧,地址: XZbar 

2. 一些代碼的說明:

    (1)CameraConfigurationManager.java

   這裏作了以下的修改:

public void initFromCameraParameters(Camera camera) {
		Camera.Parameters parameters = camera.getParameters();
		
		WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		Display display = manager.getDefaultDisplay();
		Point theScreenResolution = new Point();
		theScreenResolution = getDisplaySize(display);

		screenResolution = theScreenResolution;
		Log.i(TAG, "Screen resolution: " + screenResolution);
		
		/** 因爲換成了豎屏顯示,所以不替換屏幕寬高得出的預覽圖是變形的 */
		//如果爲豎屏就替換
		if (screenResolution.x < screenResolution.y) {
			
			Point screenResolutionForCamera = new Point();
			screenResolutionForCamera.x = screenResolution.y;
			screenResolutionForCamera.y = screenResolution.x;
			
			//findBestPreviewSizeValue  是以屏幕橫屏(W x H)數據求最佳預覽界面數據(W x H)//等到最佳預覽後,要把長寬互換,這樣對外輸出的長寬定義是一致的。
			Point cameraHW = findBestPreviewSizeValue(parameters, screenResolutionForCamera);
			cameraResolution = new Point();
			cameraResolution.x = cameraHW.y;
			cameraResolution.y = cameraHW.x;
		}
		else
		{
			cameraResolution = findBestPreviewSizeValue(parameters, screenResolution);
		}

		
		Log.i(TAG, "Camera resolution x: " + cameraResolution.x);
		Log.i(TAG, "Camera resolution y: " + cameraResolution.y);
	}


cameraResolution還是有可能被改變,在這個方法 setDesiredCameraParameters ,所以在這個方法順便處理一下。


//相機並不是對所有參數都響應,有可能設置失敗。
	public void setDesiredCameraParameters(Camera camera, boolean safeMode) {
		Camera.Parameters parameters = camera.getParameters();

		if (parameters == null) {
			Log.w(TAG, "Device error: no camera parameters are available. Proceeding without configuration.");
			return;
		}

		Log.i(TAG, "Initial camera parameters: " + parameters.flatten());

		if (safeMode) {
			Log.w(TAG, "In camera config safe mode -- most settings will not be honored");
		}
		
		
		//如果爲豎屏,設置參數
        if(screenResolution.x < screenResolution.y)
        {
        	//有些設備的相機有可能不響應這個參數,所以還是在外面翻轉預覽的data[]數據
        	//parameters.setRotation(90);
        	
        	/** 設置相機預覽爲豎屏 */		
            camera.setDisplayOrientation(90);
            //preview size 總是設置爲橫屏時纔可能生效
//試驗發現,豎屏支持的preview 有 480x800,可是傳入480,800時,參數設置不成功,導致設置爲默認640x480。 如果傳800,480.設置參數成功。
    	    parameters.setPreviewSize(cameraResolution.y, cameraResolution.x);</span>
        }
        else
        {
        	parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
        }
		
		camera.setParameters(parameters);

		Camera.Parameters afterParameters = camera.getParameters();
		Camera.Size afterSize = afterParameters.getPreviewSize();
		
		//主要是因爲前面作了動作,這裏得到豎屏的preview size還是長寬顛倒的。 
		if(screenResolution.x < screenResolution.y)
        {
			if (afterSize != null && (cameraResolution.y != afterSize.width || cameraResolution.x != afterSize.height)) {
				Log.w(TAG, "Camera said it supported preview size " + cameraResolution.x + 'x' + cameraResolution.y + ", but after setting it, preview size is " + afterSize.width + 'x' + afterSize.height);
				cameraResolution.y = afterSize.width;
				cameraResolution.x = afterSize.height;
			}
        }else
        {
        	if (afterSize != null && (cameraResolution.x != afterSize.width || cameraResolution.y != afterSize.height)) {
    			Log.w(TAG, "Camera said it supported preview size " + cameraResolution.x + 'x' + cameraResolution.y + ", but after setting it, preview size is " + afterSize.width + 'x' + afterSize.height);
    			cameraResolution.x = afterSize.width;
    			cameraResolution.y = afterSize.height;
    		}
        }
	}

(2)DecodeHandler.java

這個文件,主要是對previewcallback 的數據做處理。


主要是幾個方法的說明:

Image barcode = new Image(size.width, size.height,"Y800");  // 創建一個image 大小和preview size一致

barcode.setData(rotatedData);                                //填充數據

barcode.setCrop( rect.left ,rect.top, rect.width(),rect.height());//圈出識別區域

int ret =scanner.scanImage(barcode);     //掃描


難點就是怎麼確定識別區域? 這個在前面的參考連接中有詳細的說明。不過因爲我的全屏,所以不用得到狀態欄的高度。



	/**
	 * Decode the data within the viewfinder rectangle, and time how long it
	 * took. For efficiency, reuse the same reader objects from one decode to
	 * the next.
	 * 
	 * @param data
	 *            The YUV preview frame.
	 * @param width
	 *            The width of the preview frame.
	 * @param height
	 *            The height of the preview frame.
	 */
	private void decode(byte[] data, int width, int height) {
		Size size = activity.getCameraManager().getPreviewSize();
		Log.w("PreView size:","size : w "+ size.width + " h:" + size.height);
	
		Rect rect = activity.getCropRect();
		Log.i("CropRect","左上點:("+rect.left+","+ rect.top+") 右下點:("+rect.right+","+ rect.bottom+")寬高:width:"+rect.width()+" height:"+rect.height());
		
		byte[] rotatedData = new byte[data.length];
		if(activity.getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT)
		{
			// 這裏需要將獲取的data翻轉一下,因爲相機默認拿的的橫屏的數據
			rotatedData = new byte[data.length];
			for (int y = 0; y < size.height; y++) {
				for (int x = 0; x < size.width; x++)
					rotatedData[x * size.height + size.height - y - 1] = data[x + y * size.width];
			}
	
			// 寬高也要調整
			int tmp = size.width;
			size.width = size.height;
			size.height = tmp;
		}
		else
		{
			System.arraycopy(data, 0, rotatedData, 0, data.length);
		}
		
		//zbar解碼
		Image barcode = new Image(size.width, size.height, "Y800");
		barcode.setData(rotatedData);
		barcode.setCrop( rect.left ,rect.top, rect.width(),rect.height());
		int ret = scanner.scanImage(barcode);

		if (ret != 0)  {
            if (null != activity.getHandler()) {
            	
            	SymbolSet syms = scanner.getResults();
    			String resultString = "";
    			for (Symbol sym : syms) {
    				resultString="" + sym.getData();
    			}
    			if (null == resultString || resultString.equals(""))
    			{
    				//繼續掃描
    			}else{
    				PlanarYUVLuminanceSource source = buildLuminanceSource(rotatedData, size.width, size.height);
                    Message msg = new Message();
                    Bundle bundle = new Bundle();
                    bundleThumbnail(source, bundle);
                    msg.setData(bundle);
                    msg.obj = resultString;
                    msg.what = decode_succeeded_id;
                    activity.getHandler().sendMessage(msg);
    			}
            }
        } else {
            if (null != activity.getHandler()) {
                activity.getHandler().sendEmptyMessage(decode_failed_id);
            }
        }
	}

六,如果希望直接引入jar 來使用,使用說明在這裏。打包好的jar以及資源文件下載地址: XZabr.jar

1. AndroidManifest.xml 添加權限:

   

    <supports-screens
        android:anyDensity="true"
        android:largeScreens="true"
        android:normalScreens="true"
        android:smallScreens="true"
        android:xlargeScreens="true" />

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.FLASHLIGHT" />

    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

申明acticity

<activity android:name="com.ZbarZxing.XZbar.ZbarActivity" >
  		</activity>



2. 將資源文件放置好。




3. 放置 動態庫文件和jar包




4. 在 mainActivity 創建對象


HxBarcode hxBarcode = new HxBarcode(); 
//參數爲 context,requestCode(在下面的 onResultActivity 中有用),boolean值(是否橫屏)
hxBarcode.scan(MainActivity.this, 501,true); 


成功後,回調


@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		// TODO Auto-generated method stub
		super.onActivityResult(requestCode, resultCode, data);
		System.out.printf("onActivityResult : %s,%s\n",requestCode,resultCode);
		switch(requestCode)
		{
		case 501:
			if(data!=null)
			{
				Bundle extras = data.getBundleExtra("data");
				


				if (null != extras) {
					int width = extras.getInt("width");
					int height = extras.getInt("height");
					String result = extras.getString("result");
					mResultText.setText(result);
					
					
					//以下只是爲了顯示圖片。
					LayoutParams lps = new LayoutParams(width,height);
					lps.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());
					lps.leftMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
					lps.rightMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
					
					mResultImage.setLayoutParams(lps);
					Bitmap barcode = null;
					byte[] compressedBitmap = extras.getByteArray(DecodeThread.BARCODE_BITMAP);
					if (compressedBitmap != null) {
						barcode = BitmapFactory.decodeByteArray(compressedBitmap, 0, compressedBitmap.length, null);
						// Mutable copy:
						barcode = barcode.copy(Bitmap.Config.RGB_565, true);
					}
					mResultImage.setImageBitmap(barcode);
				}
			}
			break;
		}
	}

 



打完了。回饋完了。


2015.2.9 號  增加相冊選擇圖片解碼



   有網友需要從圖片解碼,我自己也沒時間實現。

   我的思路是:

    1. 取得圖片的原始數據(不包含頭信息什麼的),這樣就跟預覽時一樣了。問題是我用 bitmap.getPixels取得數據是 int數組,應該是ARGB格式。barcode.setData(int[] in)試驗不能解碼。(結果不成功,思路應該對吧。)

    

    2. 取得圖片的純RGB數據,再轉爲預覽時回調的那種數據格式,可能是YUV42sp之類的,結果還是不懂怎麼轉爲YUV42sp,而且還要保證,出來的數據是byte數組。(卡了)



然後今天,無意看到 Image barcode = new Image(W,H,"GREY")可以指定爲灰度數據解析。而我看了一下ARGB數據,A一直爲 0xff,也就是透明度不用管了。我將RGB用灰度公式轉成灰度值,並放在byte數組,這樣就剛好了。於是選擇圖片解碼,終於可以了。哇咔咔。 


(1)ZbarActivity.java中,在addLisenter()  增加一個按鍵監聽


gallery.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
				intent.setType("image/*");
				intent.putExtra("crop", "true");
				intent.putExtra("aspectX", 3);
				intent.putExtra("aspectY", 2);
				intent.putExtra("outputX", 600);
				intent.putExtra("outputY", 400);
				intent.putExtra("scale", true);
				intent.putExtra("return-data", false);
				intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
				intent.putExtra("outputFormat",Bitmap.CompressFormat.JPEG.toString());
				intent.putExtra("noFaceDetection", true); // no face detection 
				startActivityForResult(intent,PHOTO_REQUEST_CUT);
			}
		});


增加 onActivityResult

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		// TODO Auto-generated method stub
		super.onActivityResult(requestCode, resultCode, data);

		if (requestCode == PHOTO_REQUEST_CUT
				&& resultCode == Activity.RESULT_OK) 
		{
			if (imageUri != null) {
				Bitmap bitmap = decodeUriAsBitmap(imageUri);// decode bitmap
				int W = bitmap.getWidth();
				int H = bitmap.getHeight();
				int[] photodata = new int[W * H];
				bitmap.getPixels(photodata, 0, W, 0, 0, W, H); //獲取圖片原始ARGB數據

				//將RGB轉爲灰度數據。
				byte[] greyData = new byte[W * H];
				for (int i = 0; i < greyData.length; i++) {
					greyData[i] = (byte) ((((photodata[i] & 0x00ff0000) >> 16)
							* 19595 + ((photodata[i] & 0x0000ff00) >> 8)
							* 38469 + ((photodata[i] & 0x000000ff)) * 7472) >> 16);
				}

				Image barcode = new Image(W, H, "GREY"); 
				barcode.setData(greyData);
				ImageScanner scanner = new ImageScanner();
				int ret = scanner.scanImage(barcode);

				Log.e("zbh", "" + ret);
				if (ret != 0) {

					SymbolSet syms = scanner.getResults();
					String resultString = "";
					for (Symbol sym : syms) {
						resultString = "" + sym.getData();
						Log.e("zbh", "sym.getData()" + resultString);
					}
					if (null == resultString || resultString.equals("")) {

					} else {
						Bundle resultbundle = new Bundle();

						resultbundle.putByteArray(DecodeThread.BARCODE_BITMAP,
								Bitmap2Bytes(bitmap));

						inactivityTimer.onActivity();
						beepManager.playBeepSoundAndVibrate();
						Intent dataStr = new Intent();

						resultbundle.putInt("width", W);
						resultbundle.putInt("height", H);
						resultbundle.putString("result", resultString);
						dataStr.putExtra("data", resultbundle);
						setResult(Activity.RESULT_OK, dataStr);
						finish();
					}
				} else {
					// 解碼失敗
					Intent dataStr = null;
					setResult(Activity.RESULT_CANCELED, dataStr);
					finish();
				}
			}
		}
	}
	
	public byte[] Bitmap2Bytes(Bitmap bm) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
		return baos.toByteArray();
	}
	
	private Bitmap decodeUriAsBitmap(Uri uri){
	    Bitmap bitmap = null;
	    try {
	        bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
	    } catch (FileNotFoundException e) {
	        e.printStackTrace();
	        return null;
	    }
	    return bitmap;
	}


增加兩個變量:


    private String IMAGE_FILE_LOCATION="file://"+Environment.getExternalStorageDirectory().getAbsolutePath()+"/temp.jpg";

    Uri imageUri=Uri.parse(IMAGE_FILE_LOCATION);//The Uri to store the big bitmap


修改 

// Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);

爲 Intent intent = new Intent(Intent.ACTION_PICK, null);


因爲發現在 4.4 我的華爲手機,老是提示 文件沒找到 異常。 修改後正常。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章