圖文混合驗證碼實現


本節實現效果:在圖片內嵌入文字實現驗證碼;
核心知識: 提取圖片指定區域主色調;


重要說明:代碼中所涉及的坐標均基于圖片實際像素尺寸坐標,并非UIImageView的尺寸坐標;

實現步驟:
1.獲取圖片指定區域主要色彩;
2.在圖片指定位置繪制文字做旋轉.模糊.變形等處理;

一.效果如下:


圖一:圖片色調跳躍較大的情況
圖二:圖片色調較平滑的情況
圖三:圖片色調平滑的情況

可以明顯的看出:
1.在圖一的中文字過于明顯,容易被提取特征點后分析識別;
2.在圖二中局部區域比較理想,對于較明亮處依然存在圖一的問題;
3.在圖三中整體效果尚且可以,但是文字太暗,用戶體驗較差;
針對以上問題,其實都是圖片本身過于明亮所致,故在重新繪制圖片是選擇- (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha方法,手動調節圖片透明度即可,甚至可以給圖片覆蓋特定的前景色;
當然,既然獲取了文字所在區域的像素值,人為制造噪點也是可以的!!!

二.獲取圖片指定區域主要色彩


主要代碼如下:

-(UIColor*)mostColor:(UIImage*)image atRegion:(CGRect)region{
    
    CGImageRef inImage = image.CGImage;
    // Create off screen bitmap context to draw the image into. Format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue
    CGContextRef cgctx = [self createARGBBitmapContextFromImage:inImage];//該方法下文會講解
    if (cgctx == NULL) {
        return nil; /* error */
    }
    
    size_t w = CGImageGetWidth(inImage);
    size_t h = CGImageGetHeight(inImage);
    CGRect rect = {{0,0},{w,h}};
    
    // 繪制
    CGContextDrawImage(cgctx, rect, inImage);
    
    // Now we can get a pointer to the image data associated with the bitmap
    // 對應像素點從左向右 Z 字型保存,所在在獲取特定點時  offset = (w*y)+x
    unsigned char* data = (unsigned char*)CGBitmapContextGetData(cgctx);
     NSCountedSet *cls=[NSCountedSet setWithCapacity:region.size.width*region.size.height];
    
    // 獲取坐標 x,y 處的像素點顏色值 ARGB
    for (NSInteger x = region.origin.x ; x<region.origin.x + region.size.width ; x++) {
        for (NSInteger y = region.origin.y; y< region.origin.y + region.size.height; y++) {
            
            NSInteger offset = 4*((w*y)+x);  //對應像素點從左向右 Z 字型保存
            if (offset + 4 >= w*h*4) {
                break;
            }
            NSInteger alpha = data[offset];
            NSInteger red   = data[offset + 1] ;
            NSInteger green = data[offset+2];
            NSInteger blue  = data[offset+3];
            
            [cls addObject:@[@(red),@(green),@(blue),@(alpha)]];
        }
    }
    
    CGContextRelease(cgctx);
    if (data) { free(data); }
    
    // 找到出現次數最多的那個顏色
    NSEnumerator *enumerator = [cls objectEnumerator];
    NSArray *curColor = nil;
    NSArray *MaxColor = nil;
    NSUInteger MaxCount=0;
    
    while ( (curColor = [enumerator nextObject])){
        NSUInteger tmpCount = [cls countForObject:curColor];
        if ( tmpCount < MaxCount ) continue;
        MaxCount = tmpCount;
        MaxColor = curColor;
    }
    
    return [UIColor colorWithRed:([MaxColor[0] intValue]/255.0f) green:([MaxColor[1] intValue]/255.0f) blue:([MaxColor[2] intValue]/255.0f) alpha:1.f/*([MaxColor[3] intValue]/255.0f)*/];
}

上述代碼沒什么特別的地方,提一句NSCountedSet, NSCountedSet繼承自NSSet,意味著NSCountedSet也不能存儲相同的元素,但是,這個但是很及時,當NSCountedSet添加相同的元素時,會維護一個計數器,記錄當前元素添加的次數,隨后可以調用 countForObject獲取對應元素存儲次數;

CGImageRef 到 CGContextRef轉換:

- (CGContextRef) createARGBBitmapContextFromImage:(CGImageRef) inImage {
    
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    long             bitmapByteCount;
    long             bitmapBytesPerRow;
    
    // Get image width, height. We'll use the entire image.
    size_t pixelsWide = CGImageGetWidth(inImage);
    size_t pixelsHigh = CGImageGetHeight(inImage);
    
    // Declare the number of bytes per row. Each pixel in the bitmap in this
    // example is represented by 4 bytes; 8 bits each of red, green, blue, and
    // alpha.
    bitmapBytesPerRow   = (pixelsWide * 4);
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
    
    // Use the generic RGB color space.
    colorSpace = CGColorSpaceCreateDeviceRGB();
    
    if (colorSpace == NULL){
        fprintf(stderr, "Error allocating color space\n");
        return NULL;
    }
    
    // Allocate memory for image data. This is the destination in memory
    // where any drawing to the bitmap context will be rendered.
    bitmapData = malloc( bitmapByteCount );
    if (bitmapData == NULL){
        fprintf (stderr, "Memory not allocated!");
        CGColorSpaceRelease( colorSpace );
        return NULL;
    }
    
    // Create the bitmap context. We want pre-multiplied ARGB, 8-bits
    // per component. Regardless of what the source image format is
    // (CMYK, Grayscale, and so on) it will be converted over to the format
    // specified here by CGBitmapContextCreate.
    context = CGBitmapContextCreate (bitmapData,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,      // bits per component
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedFirst);
    if (context == NULL){
        free (bitmapData);
        fprintf (stderr, "Context not created!");
    }
    
    // Make sure and release colorspace before returning
    CGColorSpaceRelease( colorSpace );
    
    return context;
}

三.在圖片指定位置繪制文字做旋轉.模糊.變形等處理

-(UIImage *)drawTitles:(NSArray<NSString *> *)titles regions:(NSArray<NSString *> *)rects onImage:(UIImage *)sourceImage{
    //原始image的寬高
    CGFloat viewWidth = sourceImage.size.width;
    CGFloat viewHeight = sourceImage.size.height;
    //為了防止圖片失真,繪制區域寬高和原始圖片寬高一樣
    UIGraphicsBeginImageContext(CGSizeMake(viewWidth, viewHeight));
//    [[UIColor lightGrayColor] setFill];
//    UIRectFill(CGRectMake(0, 0, viewWidth, viewHeight));
    //先將原始image繪制上
    [sourceImage drawInRect:CGRectMake(0, 0, viewWidth, viewHeight) blendMode:kCGBlendModeOverlay alpha:0.9f];
//    [sourceImage drawInRect:CGRectMake(0, 0, viewWidth, viewHeight)];
    //旋轉上下文矩陣,繪制文字
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    for (int i = 0; i < titles.count; i++) {
        NSString *title = titles[i];
        // 文字所在位置
        CGRect region = CGRectFromString(rects[i]);
        // 主色調
        UIColor *mostColor = [self mostColor:sourceImage atRegion:region];
        // 隨機旋轉角
        CGFloat ratation = (M_PI / (rand() % 10 ));
    
        // 不建議隨機處理陰影,避免大概率出現文字無法顯示問題
        NSShadow *shadow = [[NSShadow alloc] init];
        shadow.shadowBlurRadius = 5;
        shadow.shadowColor = mostColor;
        shadow.shadowOffset = CGSizeMake(3  ,4);
        
        // 添加文本顏色和陰影等效果
        NSDictionary *attr = @{
                               //設置字體大小,可自行隨機
                               NSFontAttributeName: [UIFont systemFontOfSize:100],
                               //設置文字顏色
                               NSForegroundColorAttributeName :mostColor,
                               NSShadowAttributeName:shadow,
                               NSVerticalGlyphFormAttributeName:@(0),
                               };
       
        NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:title attributes:attr];
        //將繪制原點(0,0)調整到源文字的中心
        CGContextConcatCTM(context, CGAffineTransformMakeTranslation(region.origin.x + region.size.width / 2.f, region.origin.y + region.size.height / 2.f));
        // 以源文字的中心為中心旋轉
        CGContextConcatCTM(context, CGAffineTransformMakeRotation(ratation));
        // 將繪制原點恢復初始值,保證當前context中心和源image的中心處在一個點(當前context已經旋轉,所以繪制出的任何layer都是傾斜的)
        CGContextConcatCTM(context, CGAffineTransformMakeTranslation(-region.origin.x - region.size.width / 2.f, -region.origin.y -region.size.height / 2.f));
        // 繪制源文字
        [title drawInRect:CGRectMake(region.origin.x , region.origin.y, attrStr.size.width, attrStr.size.height) withAttributes:attr];
    }
    UIImage *finalImg = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    CGContextRestoreGState(context);
    return finalImg;
}

注釋已經很詳細了,不做贅述,直接看一個例子:

特別注意:
1.- (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha方法;
2.所有坐標均是基于圖片本身像素尺寸,并非UIIMageView;

本例中所用圖片 image.size 為:800*800,
注意需要保證rectN 的x+width < image.size.width 并且 y+height < image.size. height才可正確繪制,實際可根據自身情況對rectN采用合適的方式隨機生成;

    // 按照圖片尺寸隨機即可
    NSString *rect1 = NSStringFromCGRect(CGRectMake(100, 350, 100, 100));
    NSString *rect2 = NSStringFromCGRect(CGRectMake(200, 400, 100, 100));
    NSString *rect3 = NSStringFromCGRect(CGRectMake(350, 200, 100, 100));
    NSString *rect4 = NSStringFromCGRect(CGRectMake(550, 300, 100, 100));
    
    self.filterImageView.image = [self drawTitles:@[@"測",@"試",@"字",@"體"]
                                          regions:@[rect1,rect2,rect3,rect4]
                                          onImage:self.originalImageView.image];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,615評論 25 708
  • HTML標簽解釋大全 一、HTML標記 標簽:!DOCTYPE 說明:指定了 HTML 文檔遵循的文檔類型定義(D...
    米塔塔閱讀 3,285評論 1 41
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 13,774評論 1 92
  • 請把我的生命交給你吧 讓你的唇吻過我的每一寸肌膚 讓我醉死在你的溫情里 請在清晨的霞光到來之際 給我帶上你編織的花...
    耀坤Rosy閱讀 448評論 0 6
  • 就在昨天,我吸到了我從小到大吸過的濃度最大的霾。我知道我還太年輕,沒見過什么世面,自以為吸了口對廣大群眾來說超級普...
    懶惰的家伙閱讀 482評論 0 0