react-native-camera插件支持限制扫描区域

实现原理

Android二维码扫描限制扫描区域的实现方式是通过修改相机预览图片截取灰度图的范围来实现限制扫描区域。相机预览尺寸因手机而不同,相机会选择一个相等的或相近的尺寸用来做预览尺寸,根据这个尺寸提取出预览图片的YUV数据。YUV数据区别于RGB数据,YUV数据将像素的灰度值(明亮度)与色值(色彩与饱和度)区分开,分别来存。我们的插件中使用的数据格式是YUV420格式。它的存储方式是先将每个像素的Y值(灰度值)存储起来,这样就是一个width * height大小的数组。另外再存储UV值,每四个像素共用一个U值和V值,所以U = V = Y / 4。最终获得的数组大小是width * height * 3 / 2。这样存储的好处是,即使只拿到了Y值也是可以渲染图片的,只不过渲染的是黑白图片。因为二维码是二维平面图片,只提取黑白颜色是能够识别二维码信息的。所以利用PlanarYUVLuminanceSource类将截取区域的X,Y,width,height值设置进去,在getMatrix(获取灰度图矩阵)时就会只获取这一区域的YUV数据。我们把X,Y,width,height值设置为二维码扫描框的大小和位置,这样在识别二维码时就只能识别扫描框中的二维码了。

YUV数据格式参考

iOS二维码扫描限制扫描区域是通过rectOfInterest方法来实现的,它的实现方式要比Android的方法更简单,因为iOS的原生代码支持这样的设置,我们只需要将参数传进去就可以了。但是一个需要特别注意的地方是坐标系发生了变换。常规的坐标系是左上角为原点,向右为X轴,向下为Y轴。而在iOS的rectOfInterest方法传入的参数是以右上角为原点,向左为X轴,向下为Y轴。这样X,Y,width,height四个值就要发生变换才可以。

X -> Y / SCREEN_HEIGHT
Y -> (SCREEN_WIDTH - X - 扫描框宽度)/ SCREEN_WIDTH
width -> 扫描框高度 / SCREEN_HEIGHT
height -> 扫描框宽度 / SCREEN_WIDTH

iOS 二维码有效区域rectOfInterest详解

示例:self.output.rectOfInterest = CGRectMake(100 / (SCREEN_HEIGHT), (SCREEN_WIDTH - 100 - 200) / SCREEN_WIDTH, 100 / SCREEN_HEIGHT, 200 / SCREEN_WIDTH);//200 * 100的原点在(100,100)位置的扫描框

需要注意的是,设置扫描框区域范围时会受到屏幕宽高比设置的影响。一般我们的手机屏幕宽高比都是16:9的,插件中默认设置的是4:3,如果我们不相应的设置屏幕宽高比,则设置完扫描区域后会发现在Y轴方向上能够实现对扫描区域的控制,但是在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中用绝对布局画一个框来看一下这个扫描区域的范围。

<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 用来控制是否开启扫描区域限制
scanAreaX int 0 扫描区域原点X值
scanAreaY int 0 扫描区域原点Y值
scanAreaWidth int 0 扫描区域宽度
scanAreaHeight int 0 扫描区域高度

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);
}
/*...*/
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 二维码扫描最近两年简直是风靡移动互联网时代,尤其在国内发展神速。围绕条码扫码功能,首先说说通过本文你可以知道啥。一...
    55book阅读 4,134评论 0 1
  • 文章图片上传不正常,如需文档,可联系微信:1017429387 目录 1 安装... 4 1.1 配置探针... ...
    Mrhappy_a7eb阅读 6,276评论 0 5
  • 选择qi:是表达式 标签选择器 类选择器 属性选择器 继承属性: color,font,text-align,li...
    wzhiq896阅读 1,727评论 0 2
  • 对网页有些了解的朋友应该都知道并不是所有网页的页面内容都在页面源码中,大多数网页源码中只有页面结构而无数据,显示的...
    GHope阅读 1,284评论 0 17
  • 2018年第38周(09/17--09/23) 这一周一切都在正常轨道进行··· 一、健康 1.早起 本周早起时间...
    tracy_bacb阅读 83评论 0 0