flutter 實戰(zhàn) 之 生成證件照/免冠照

在網(wǎng)上翻了一圈發(fā)現(xiàn)并沒有人貢獻這方面的代碼可能是技術(shù)含量太低了,我這里記錄以下
用到的工具
1.權(quán)限管理http://www.reibang.com/p/35b37c012351

2.dio 網(wǎng)絡(luò) http://www.reibang.com/p/fdd12bb921c2

3. 圖片的操作 http://www.reibang.com/p/bf97573eb69e http://www.reibang.com/p/c276b27520c0

實現(xiàn)原理

通過拍照/選取圖片 (image_picker)
裁切尺寸大小圖片(image_cropper)
將文件轉(zhuǎn)成Base64 上傳百度AI進行輪廓裁剪(百度AI)
通過Canvas繪畫合成
將文件保存到本地并顯示在widget上

選取圖片

  ///選擇一個圖片
  ///[from] 是相機還是圖庫
  ///可選參數(shù)
  ///[maxWidth] 寬度,
  ///[maxHeight] 高度,
  ///[imageQuality] 質(zhì)量
  static pickSinglePic(ImageFrom from,
      {double? maxWidth, double? maxHeight, int? imageQuality}) async {
    ImageSource source;
    switch (from) {
      case ImageFrom.camera:
        source = ImageSource.camera;
        break;
      case ImageFrom.gallery:
        source = ImageSource.gallery;
        break;
    }
    final pickerImages = await ImagePicker().pickImage(
      source: source,
      imageQuality: imageQuality,
      maxWidth: maxWidth,
      maxHeight: maxHeight,
    );
    return pickerImages;
  }

裁切圖片大小

  ///裁切圖片
  ///[image] 圖片路徑或文件
  ///[width] 寬度
  ///[height] 高度
  ///[aspectRatio] 比例
  ///[androidUiSettings]UI 參數(shù)
  ///[iOSUiSettings] ios的ui 參數(shù)
  static cropImage(
      {required image,
      required width,
      required height,
      aspectRatio,
      androidUiSettings,
      iOSUiSettings}) async {
    String imagePth = "";
    if (image is String) {
      imagePth = image;
    } else if (image is File) {
      imagePth = image.path;
    } else {
      throw ("文件路徑錯誤");
    }
    final croppedFile = await ImageCropper().cropImage(
      sourcePath: imagePth,
      maxWidth: FormatUtil.num2int(width),
      maxHeight: FormatUtil.num2int(height),
      aspectRatio: aspectRatio ??
          CropAspectRatio(
              ratioX: FormatUtil.num2double(width),
              ratioY: FormatUtil.num2double(height)),
      uiSettings: [
        androidUiSettings ??
            AndroidUiSettings(
                toolbarTitle:
                    '圖片裁切(${FormatUtil.num2int(width)}*${FormatUtil.num2int(height)})',
                toolbarColor: Colors.blue,
                toolbarWidgetColor: Colors.white,
                initAspectRatio: CropAspectRatioPreset.original,
                hideBottomControls: false,
                lockAspectRatio: true),
        iOSUiSettings ??
            IOSUiSettings(
              title: 'Cropper',
            ),
      ],
    );
    return croppedFile;
  }

將獲取到的圖片轉(zhuǎn)成Base64 上傳

  ///圖片轉(zhuǎn)base64 無頭
  ///[file] 文件
  ///[base64] String 類型的base64
  static file2Base64(File file) async {
    Uint8List imageBytes = await file2Uint8List(file);
    String base64 = base64Encode(imageBytes);
    return base64;
  }
  ///文件轉(zhuǎn) Uint8List
  static file2Uint8List(File file) async {
    Uint8List imageBytes = await file.readAsBytes();
    return imageBytes;
  }

上傳百度AI

  static Future<String> getToken() async {
    /// 第一個必穿參數(shù) 第二個第三個自己去百度Ai官網(wǎng)申請 免費額度一萬次
    Map<String, dynamic> param = {
      "grant_type": "client_credentials",
      "client_id": clientId,
      "client_secret": clientSecret
    };
    Response<dynamic> response =
        await DioClient().doPost(Api.getToken, queryParameters: param);
    return response.data["access_token"];
  }

  /// 獲取文件Base64的前景照 這里用的fromData傳輸
  static Future<String> getBase64(Map<String, dynamic> param) async {
    Response<dynamic> response = await DioClient().doPost(Api.uploadURL,
        data: FormData.fromMap(param),
        options: Options(contentType: "application/x-www-form-urlencoded"));
    return response.data["foreground"];
  }

獲取到的Base64畫到Cnvas上

 ///創(chuàng)建一個圖片的 ByteData
  ///[width] 圖片寬度
  ///[height] 圖片高度
  ///[bgColor] 背景顏色
  ///[imageFile] base64的圖片路徑(無頭)
  static generateImageData(
      {required double width,
      required double height,
      bgColor = Colors.white,
      imageFile}) async {
    ///canvas的記錄工具 用來保存canvas的
    final recorder = ui.PictureRecorder();

    ///canvas 繪圖工具
    Canvas canvas = Canvas(recorder);

    ///畫筆 顏色為傳入顏色 狀態(tài)是填充
    Paint paint = Paint();
    paint.color = bgColor;
    paint.style = PaintingStyle.fill;

    ///底下跟我畫個背景
    canvas.drawRect(Rect.fromLTWH(0, 0, width, height), paint);

    ///頂上再畫個人
    paint.color = Colors.black;
    paint.style = PaintingStyle.stroke;
    paint.strokeWidth = 10;
    ui.Image image = await base64ToImage(imageFile,
        width: width.toInt(), height: height.toInt());
    canvas.drawImage(image, Offset.zero, paint);
    // 轉(zhuǎn)換成圖片
    ///記錄畫的canvas
    Picture picture = recorder.endRecording();

    return await picture2ByteData(picture, width, height);
  }

將Picture轉(zhuǎn)成ByteData再轉(zhuǎn)成Uint8list負責顯示和保存

  ///獲取到的picture 轉(zhuǎn)換成 ByteData
  ///[picture] canvas畫然后記錄的文件
  ///[width] 寬度
  ///[height] 高度
  static Future<ByteData?> picture2ByteData(
      ui.Picture picture, double width, double height) async {
    ui.Image img = await picture.toImage(width.toInt(), height.toInt());

    debugPrint('img的尺寸: $img');

    ByteData? byteData = await img.toByteData(format: ui.ImageByteFormat.png);

    return byteData;
  }
/// 將ByteData 轉(zhuǎn)成 Uint8List
  /// [data] ByteData數(shù)據(jù)
  /// return [Uint8List] Uint8List
  static byteData2Uint8List(ByteData data) {
    return data.buffer.asUint8List();
  }

保存圖片

///保存Uint8List 到相冊
 ///[image]Uint8List 數(shù)組
 ///[quality] 質(zhì)量
 ///[name] 保存的名字
 static saveImage2Album(image, {quality = 100, name = "photo"}) async {
   final result =
       await ImageGallerySaver.saveImage(image, quality: quality, name: name);
   return result;
 }

完整demo 在 https://gitee.com/half_city/flutter_identification_photo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末担敌,一起剝皮案震驚了整個濱河市蒿讥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖枚钓,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機遂赠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晌杰,“玉大人跷睦,你說我怎么就攤上這事『趵颍” “怎么了送讲?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵奸笤,是天一觀的道長惋啃。 經(jīng)常有香客問我,道長监右,這世上最難降的妖魔是什么边灭? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮健盒,結(jié)果婚禮上绒瘦,老公的妹妹穿的比我還像新娘。我一直安慰自己扣癣,他們只是感情好惰帽,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著父虑,像睡著了一般该酗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上士嚎,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天呜魄,我揣著相機與錄音,去河邊找鬼莱衩。 笑死爵嗅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的笨蚁。 我是一名探鬼主播睹晒,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼趟庄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伪很?” 一聲冷哼從身側(cè)響起岔激,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎是掰,沒想到半個月后虑鼎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡键痛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年炫彩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片絮短。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡江兢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丁频,到底是詐尸還是另有隱情杉允,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布席里,位于F島的核電站叔磷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奖磁。R本人自食惡果不足惜改基,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咖为。 院中可真熱鬧秕狰,春花似錦、人聲如沸躁染。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吞彤。三九已至我衬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間备畦,已是汗流浹背低飒。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留懂盐,地道東北人褥赊。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像莉恼,于是被迫代替她去往敵國和親拌喉。 傳聞我的和親對象是個殘疾皇子速那,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容