實現(xiàn)原理
Android二維碼掃描限制掃描區(qū)域的實現(xiàn)方式是通過修改相機預覽圖片截取灰度圖的范圍來實現(xiàn)限制掃描區(qū)域。相機預覽尺寸因手機而不同,相機會選擇一個相等的或相近的尺寸用來做預覽尺寸,根據這個尺寸提取出預覽圖片的YUV數(shù)據堡掏。YUV數(shù)據區(qū)別于RGB數(shù)據,YUV數(shù)據將像素的灰度值(明亮度)與色值(色彩與飽和度)區(qū)分開,分別來存岂却。我們的插件中使用的數(shù)據格式是YUV420格式。它的存儲方式是先將每個像素的Y值(灰度值)存儲起來裙椭,這樣就是一個width * height大小的數(shù)組躏哩。另外再存儲UV值,每四個像素共用一個U值和V值揉燃,所以U = V = Y / 4扫尺。最終獲得的數(shù)組大小是width * height * 3 / 2。這樣存儲的好處是炊汤,即使只拿到了Y值也是可以渲染圖片的正驻,只不過渲染的是黑白圖片。因為二維碼是二維平面圖片抢腐,只提取黑白顏色是能夠識別二維碼信息的姑曙。所以利用PlanarYUVLuminanceSource類將截取區(qū)域的X,Y氓栈,width渣磷,height值設置進去,在getMatrix(獲取灰度圖矩陣)時就會只獲取這一區(qū)域的YUV數(shù)據授瘦。我們把X醋界,Y,width提完,height值設置為二維碼掃描框的大小和位置形纺,這樣在識別二維碼時就只能識別掃描框中的二維碼了。
iOS二維碼掃描限制掃描區(qū)域是通過rectOfInterest方法來實現(xiàn)的徒欣,它的實現(xiàn)方式要比Android的方法更簡單逐样,因為iOS的原生代碼支持這樣的設置,我們只需要將參數(shù)傳進去就可以了。但是一個需要特別注意的地方是坐標系發(fā)生了變換脂新。常規(guī)的坐標系是左上角為原點挪捕,向右為X軸,向下為Y軸争便。而在iOS的rectOfInterest方法傳入的參數(shù)是以右上角為原點级零,向左為X軸,向下為Y軸滞乙。這樣X奏纪,Y,width斩启,height四個值就要發(fā)生變換才可以序调。
X -> Y / SCREEN_HEIGHT
Y -> (SCREEN_WIDTH - X - 掃描框寬度)/ SCREEN_WIDTH
width -> 掃描框高度 / SCREEN_HEIGHT
height -> 掃描框寬度 / SCREEN_WIDTH
iOS 二維碼有效區(qū)域rectOfInterest詳解
示例:self.output.rectOfInterest = CGRectMake(100 / (SCREEN_HEIGHT), (SCREEN_WIDTH - 100 - 200) / SCREEN_WIDTH, 100 / SCREEN_HEIGHT, 200 / SCREEN_WIDTH);//200 * 100的原點在(100,100)位置的掃描框
需要注意的是,設置掃描框區(qū)域范圍時會受到屏幕寬高比設置的影響兔簇。一般我們的手機屏幕寬高比都是16:9的发绢,插件中默認設置的是4:3,如果我們不相應的設置屏幕寬高比男韧,則設置完掃描區(qū)域后會發(fā)現(xiàn)在Y軸方向上能夠實現(xiàn)對掃描區(qū)域的控制朴摊,但是在X軸上則會比設置的值要寬一些,這應該是屏幕的預覽效果在4:3的寬高比設置下被拉伸所引起的誤差此虑。所以需要設置合適的屏幕寬高比。
Android的設置方式是在js端設置屬性ratio:'16:9'口锭。全面屏的手機的比值一般為'2:1'朦前。
iOS的設置方式是在js端設置屬性defaultVideoQuality:RNCamera.Constants.VideoQuality["720p"]。
更多的設置方式參考react-native-camera官方文檔
使用示例
<RNCamera
style={styles.preview}
ratio={'16:9'}
defaultVideoQuality={RNCamera.Constants.VideoQuality["720p"]}
scanAreaLimit={true}
scanAreaX={115}
scanAreaY={328}
scanAreaWidth={522}
scanAreaHeight={521}
flashMode={this.state.torchState == 'off' ? RNCamera.Constants.FlashMode.off : RNCamera.Constants.FlashMode.torch}
onBarCodeRead={this._onBarCodeRead.bind(this)}
permissionDialogTitle={'請求相機權限'}
permissionDialogMessage={'應用沒有獲取到相機權限鹃操,請先到設置中為應用開啟相機權限'}
</RNCamera>
可以通過在View中用絕對布局畫一個框來看一下這個掃描區(qū)域的范圍韭寸。
<View style={{width: 522 * winWidth / 750, height: 521 * winHeight / 1334, position:'absolute', left: 115 * winWidth / 750, top: 328 * winHeight / 1334, borderWidth: 1, borderColor: 'red', borderStyle: 'solid', backgroundColor: 'transparent'}}>
</View>
插件代碼修改
js端
RNCamera.js增加五個屬性
屬性名 | 類型 | 默認值 | 描述 |
---|---|---|---|
scanAreaLimit | boolean | false | 用來控制是否開啟掃描區(qū)域限制 |
scanAreaX | int | 0 | 掃描區(qū)域原點X值 |
scanAreaY | int | 0 | 掃描區(qū)域原點Y值 |
scanAreaWidth | int | 0 | 掃描區(qū)域寬度 |
scanAreaHeight | int | 0 | 掃描區(qū)域高度 |
Android端
官方#1667號修改被還原
CameraViewManager.java
@ReactProp(name = "scanAreaLimit")
public void setScanAreaLimit(RNCameraView view, boolean scanAreaLimit) {
view.setScanAreaLimit(scanAreaLimit);
}
@ReactProp(name = "scanAreaX")
public void setScanAreaX(RNCameraView view, int scanAreaX) {
view.setScanAreaX(scanAreaX);
}
@ReactProp(name = "scanAreaY")
public void setScanAreaY(RNCameraView view, int scanAreaY) {
view.setScanAreaY(scanAreaY);
}
@ReactProp(name = "scanAreaWidth")
public void setScanAreaWidth(RNCameraView view, int scanAreaWidth) {
view.setScanAreaWidth(scanAreaWidth);
}
@ReactProp(name = "scanAreaHeight")
public void setScanAreaHeight(RNCameraView view, int scanAreaHeight) {
view.setScanAreaHeight(scanAreaHeight);
}
RNCameraView.java
//ScanAreaLimitParams
private boolean mScanAreaLimit = false;
private int mScanAreaX = 0;
private int mScanAreaY = 0;
private int mScanAreaWidth = 0;
private int mScanAreaHeight = 0;
public void setScanAreaLimit(boolean scanAreaLimit) {
this.mScanAreaLimit = scanAreaLimit;
}
public void setScanAreaX(int scanAreaX) {
this.mScanAreaX = scanAreaX;
}
public void setScanAreaY(int scanAreaY) {
this.mScanAreaY = scanAreaY;
}
public void setScanAreaWidth(int scanAreaWidth) {
this.mScanAreaWidth = scanAreaWidth;
}
public void setScanAreaHeight(int mScanAreaHeight) {
this.mScanAreaHeight = mScanAreaHeight;
}
/*...*/
new BarCodeScannerAsyncTask(delegate, mMultiFormatReader, correctData, correctWidth, correctHeight, mScanAreaLimit, mScanAreaX, mScanAreaY, mScanAreaWidth, mScanAreaHeight).execute();
/*...*/
BarCodeScannerAsyncTask.java(核心修改)
/*...*/
private final int COMMON_WIDTH = 750;
private final int COMMON_HEIGHT = 1334;
/*...*/
/*...*/
private BinaryBitmap generateBitmapFromImageData(byte[] imageData, int width, int height, boolean scanAreaLimit, int scanAreaX, int scanAreaY, int scanAreaWidth, int scaAreaHeight) {
if(scanAreaLimit == false) {
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
imageData, // byte[] yuvData
width, // int dataWidth
height, // int dataHeight
0, // int left
0, // int top
width, // int width
height, // int height
false // boolean reverseHorizontal
);
return new BinaryBitmap(new HybridBinarizer(source));
} else {
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
imageData, // byte[] yuvData
width, // int dataWidth
height, // int dataHeight
scanAreaX * width / COMMON_WIDTH, // int left
scanAreaY * height / COMMON_HEIGHT, // int top
scanAreaWidth * width / COMMON_WIDTH, // int width
scaAreaHeight * height / COMMON_HEIGHT, // int height
false // boolean reverseHorizontal
);
return new BinaryBitmap(new HybridBinarizer(source));
}
}
/*...*/
iOS端
RNCameraManager.m
RCT_CUSTOM_VIEW_PROPERTY(scanAreaLimit, BOOL, RNCamera)
{
[view setScanAreaLimit:[RCTConvert BOOL:json]];
}
RCT_CUSTOM_VIEW_PROPERTY(scanAreaX, NSInteger, RNCamera)
{
[view setScanAreaX:[RCTConvert NSInteger:json]];
}
RCT_CUSTOM_VIEW_PROPERTY(scanAreaY, NSInteger, RNCamera)
{
[view setScanAreaY:[RCTConvert NSInteger:json]];
}
RCT_CUSTOM_VIEW_PROPERTY(scanAreaWidth, NSInteger, RNCamera)
{
[view setScanAreaWidth:[RCTConvert NSInteger:json]];
}
RCT_CUSTOM_VIEW_PROPERTY(scanAreaHeight, NSInteger, RNCamera)
{
[view setScanAreaHeight:[RCTConvert NSInteger:json]];
}
RNCamera.h
#define SCREEN_WIDTH [UIApplication sharedApplication].delegate.window.frame.size.width
#define SCREEN_HEIGHT [UIApplication sharedApplication].delegate.window.frame.size.height
#define COMMON_WIDTH 750
#define COMMON_HEIGHT 1334
@property(assign, nonatomic) BOOL scanAreaLimit;
@property(assign, nonatomic) NSInteger scanAreaX;
@property(assign, nonatomic) NSInteger scanAreaY;
@property(assign, nonatomic) NSInteger scanAreaWidth;
@property(assign, nonatomic) NSInteger scanAreaHeight;
RNCamera.m(核心修改)
//在startSession方法中加入下段代碼
/*...*/
if (self.scanAreaLimit) {
self.scanAreaX = self.scanAreaX * SCREEN_WIDTH / COMMON_WIDTH;
self.scanAreaY = self.scanAreaY * SCREEN_HEIGHT / COMMON_HEIGHT;
self.scanAreaWidth = self.scanAreaWidth * SCREEN_WIDTH / COMMON_WIDTH;
self.scanAreaHeight = self.scanAreaHeight * SCREEN_HEIGHT / COMMON_HEIGHT;
self.metadataOutput.rectOfInterest = CGRectMake((self.scanAreaY)/(SCREEN_HEIGHT), (SCREEN_WIDTH - self.scanAreaX - self.scanAreaWidth)/SCREEN_WIDTH, self.scanAreaHeight/SCREEN_HEIGHT, self.scanAreaWidth/SCREEN_WIDTH);
}
/*...*/