• iOS原生、Android 原生, flutter 三种方式给照片流添加文字(水印)


    效果图:三中代码实现的效果差不多

    0fb4a39137f44a19bc1c1cf66729a277.png

    Swift:代码

    1. import UIKit
    2. class ImageWatermarking: NSObject {
    3. static func textToImage(drawText text: String, inImage initImage: UIImage, atPoint point: CGPoint) -> UIImage {
    4. let textColor = UIColor.white
    5. let textFont = UIFont(name:"Helvetica", size: 26)!
    6. // 新建底部拼接区域
    7. let rect: CGRect = CGRect(x: 0, y: 0, width: initImage.size.width, height: 220)
    8. UIGraphicsBeginImageContext(rect.size)
    9. let context: CGContext = UIGraphicsGetCurrentContext()!
    10. context.setFillColor(UIColor.kHexRGBA(0x000000,0.3).cgColor)
    11. context.fill(rect)
    12. let imagebottom = UIGraphicsGetImageFromCurrentImageContext()
    13. UIGraphicsGetCurrentContext()
    14. let style = NSMutableParagraphStyle()
    15. style.lineSpacing = 6
    16. style.lineBreakMode = NSLineBreakMode.byTruncatingTail
    17. // 在底部拼接区域添加文字
    18. let textFontAttributes = [
    19. NSAttributedString.Key.font: textFont,
    20. NSAttributedString.Key.foregroundColor: textColor,
    21. NSAttributedString.Key.paragraphStyle: style
    22. ] as [NSAttributedString.Key : Any]
    23. imagebottom!.draw(in: CGRect(origin: CGPoint.zero, size: imagebottom!.size))
    24. let rectText = CGRect(origin: point, size: initImage.size)
    25. text.draw(in: rectText, withAttributes: textFontAttributes)
    26. let newTextImage = UIGraphicsGetImageFromCurrentImageContext()
    27. UIGraphicsEndImageContext()
    28. // 拼接原图和新建的底部区域
    29. let topImage = initImage
    30. let bottomImage = newTextImage
    31. let allSize = CGSize(width: topImage.size.width, height: topImage.size.height )
    32. UIGraphicsBeginImageContext(allSize)
    33. let areaSizeTop = CGRect(x: 0, y: 0, width: topImage.size.width, height: topImage.size.height)
    34. topImage.draw(in: areaSizeTop)
    35. let areaSizeBottom = CGRect(x: 0, y: areaSizeTop.size.height-220, width: bottomImage!.size.width, height: bottomImage!.size.height)
    36. bottomImage!.draw(in: areaSizeBottom)
    37. let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
    38. UIGraphicsEndImageContext()
    39. return newImage
    40. }
    41. }

    Swift 调用:

    1. let contentStr :String = currentTime + "\n" + address
    2. let newIMage = ImageWatermarking.textToImage(drawText: contentStr, inImage: image!, atPoint: CGPoint(x: 20, y: 40))

    Java 代码:
    1. /**
    2. * @description 形成水印工具类
    3. @param
    4. * @author ·Steve
    5. * @date 2023/7/13 10:41
    6. */
    7. public class WatermarkSettings {
    8. public static WatermarkSettings mInstance;
    9. public static Context mContext;
    10. private static String watermarkText;
    11. private static String TAG = "";
    12. /**
    13. * @description 图片添加水印的信息
    14. @param
    15. * @author ·Steve
    16. * @date 2023/7/13 10:41
    17. */
    18. public static WatermarkSettings getmInstance(Context context) {
    19. mContext = context;
    20. if (mInstance == null) {
    21. mInstance = new WatermarkSettings();
    22. }
    23. TAG = mContext.getClass().getName();
    24. return mInstance;
    25. }
    26. /**
    27. * @description 创建水印文件,以下是水印上添加的文本信息
    28. @param
    29. * @author ·Steve
    30. * @date 2023/7/13 10:42
    31. */
    32. public static Bitmap createWatermark(String patch, String contextStr) {
    33. Bitmap bitmap = BitmapFactory.decodeFile(patch);
    34. int i = readPictureDegree(patch);
    35. if (i != ExifInterface.ORIENTATION_UNDEFINED){
    36. bitmap = rotaingImageView(i, bitmap);
    37. }
    38. // 获取图片的宽高
    39. int bitmapWidth = bitmap.getWidth();
    40. int bitmapHeight = bitmap.getHeight();
    41. // 创建一个和图片一样大的背景图
    42. Bitmap bmp = Bitmap.createBitmap(bitmapWidth, bitmapHeight+ (dp2px(mContext, 0) * bitmapWidth / getScreenWidth()), Bitmap.Config.ARGB_8888);
    43. Canvas canvas = new Canvas(bmp);
    44. canvas.drawColor(Color.parseColor("#333333"));
    45. // 画背景图
    46. canvas.drawBitmap(bitmap, 0, 0, null);
    47. watermarkText = contextStr;
    48. //-------------开始绘制文字--------------
    49. if (!TextUtils.isEmpty(watermarkText)) {
    50. int screenWidth = getScreenWidth();
    51. float textSize = dp2px(mContext, 16) * bitmapWidth / screenWidth;
    52. // 绘制矩形背景
    53. Paint paint = new Paint();
    54. paint.setAntiAlias(true);
    55. paint.setColor(0x4d000000);
    56. //绘制正方形
    57. canvas.drawRect(0, bitmapHeight- (dp2px(mContext, 130) * bitmapWidth / getScreenWidth()), bitmapWidth, bitmapHeight, paint);
    58. // 创建画笔
    59. TextPaint mPaint = new TextPaint();
    60. // 文字矩阵区域
    61. Rect textBounds = new Rect();
    62. // 水印的字体大小
    63. mPaint.setTextSize(textSize);
    64. // 文字阴影
    65. mPaint.setShadowLayer(0.5f, 0f, 1f, Color.WHITE);
    66. // 抗锯齿
    67. mPaint.setAntiAlias(true);
    68. // 水印的区域
    69. mPaint.getTextBounds(watermarkText, 0, watermarkText.length(), textBounds);
    70. // 水印的颜色
    71. mPaint.setColor(Color.WHITE);
    72. StaticLayout layout = new StaticLayout(watermarkText, 0, watermarkText.length(), mPaint, (int) (bitmapWidth - textSize),
    73. Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.5F, true);
    74. // 文字开始的坐标
    75. float textX = dp2px(mContext, 8) * bitmapWidth / screenWidth;
    76. float textY = bitmapHeight;//图片的中间
    77. // float textY = dp2px(mContext, 8) * bitmapHeight / screenWidth;
    78. // 画文字
    79. canvas.translate(textX, bitmapHeight-(dp2px(mContext, 130) * bitmapWidth / getScreenWidth()));
    80. layout.draw(canvas);
    81. }
    82. //保存所有元素
    83. canvas.save();
    84. canvas.restore();
    85. return bmp;
    86. }
    87. private static int getScreenWidth() {
    88. DisplayMetrics dm = new DisplayMetrics();
    89. WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    90. wm.getDefaultDisplay().getMetrics(dm);
    91. return dm.widthPixels;
    92. }
    93. private static int dp2px(Context context, float dp) {
    94. final float scale = context.getResources().getDisplayMetrics().density;
    95. return (int) (dp * scale + 0.5f);
    96. }
    97. //获取图片旋转角度
    98. public static int readPictureDegree(String path) {
    99. int degree = 0;
    100. try {
    101. ExifInterface exifInterface = new ExifInterface(path);
    102. int orientation = exifInterface.getAttributeInt(
    103. ExifInterface.TAG_ORIENTATION,
    104. ExifInterface.ORIENTATION_NORMAL);
    105. switch (orientation) {
    106. case ExifInterface.ORIENTATION_ROTATE_90:
    107. degree = 90;
    108. break;
    109. case ExifInterface.ORIENTATION_ROTATE_180:
    110. degree = 180;
    111. break;
    112. case ExifInterface.ORIENTATION_ROTATE_270:
    113. degree = 270;
    114. break;
    115. }
    116. } catch (IOException e) {
    117. e.printStackTrace();
    118. }
    119. return degree;
    120. }
    121. public static Bitmap rotaingImageView(int angle, Bitmap bitmap) {
    122. // 旋转图片 动作
    123. Matrix matrix = new Matrix();
    124. matrix.postRotate(angle);
    125. // 创建新的图片
    126. Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
    127. bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    128. return resizedBitmap;
    129. }
    130. /**
    131. * @description 保存图片到相册
    132. @param
    133. * @author ·Steve
    134. * @date 2023/7/13 13:59
    135. */
    136. public static File saveToLocal(Bitmap bitmap,Context context) throws IOException {
    137. String bitName = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
    138. File file = new File("/sdcard/DCIM/Camera/" + bitName + ".jpg");
    139. if (file.exists()) {
    140. file.delete();
    141. }
    142. FileOutputStream out;
    143. try {
    144. out = new FileOutputStream(file);
    145. if (bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out)) {
    146. out.flush();
    147. out.close();
    148. //保存图片后发送广播通知更新数据库
    149. // Uri uri = Uri.fromFile(file);
    150. // sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
    151. Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    152. Uri uri = Uri.fromFile(file);
    153. intent.setData(uri);
    154. context.sendBroadcast(intent);
    155. }
    156. } catch (FileNotFoundException e) {
    157. e.printStackTrace();
    158. } catch (IOException e) {
    159. e.printStackTrace();
    160. }
    161. return file;
    162. }
    163. }

    Kotlin 调用:

    1. val contentStr:String = "\n" +dateTimeStr+"\n" + userStr
    2. WatermarkSettings.getmInstance(this)
    3. val bitmap: Bitmap = WatermarkSettings.createWatermark(photo.path, contentStr)
    4. val tmpFile = WatermarkSettings.saveToLocal(bitmap,this)

    Flutter 代码:

    1. /// 图片加载工具类
    2. class ImageWaterMarkUtils {
    3. //拿到图片的字节数组
    4. static Future loadImageByFile(String path) async {
    5. var list = await File(path).readAsBytes();
    6. return loadImageByUint8List(list);
    7. }
    8. //通过[Uint8List]获取图片
    9. static Future loadImageByUint8List(Uint8List list) async {
    10. ui.Codec codec = await ui.instantiateImageCodec(list);
    11. ui.FrameInfo frame = await codec.getNextFrame();
    12. return frame.image;
    13. }
    14. //图片加文字
    15. static imageAddWaterMark(String imagePath, String content) async {
    16. double width, height;
    17. //拿到Canvas
    18. ui.PictureRecorder recorder = new ui.PictureRecorder();
    19. Canvas canvas = new Canvas(recorder);
    20. //拿到Image对象
    21. ui.Image image = await loadImageByFile(imagePath);
    22. width = image.width.toDouble();
    23. height = image.height.toDouble();
    24. // 计算四边形的对角线长度
    25. // double dimension = math.sqrt(math.pow(image.width, 2) + math.pow(image.height, 2));
    26. canvas.drawImage(image, Offset(0, 0), Paint());
    27. canvas.saveLayer(Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()), Paint()..blendMode = BlendMode.multiply);
    28. Rect rectBg = Rect.fromLTWH(0, height-600, width, 600);
    29. Paint paint =Paint()
    30. ..color = const Color(0x66000000)
    31. ..strokeCap = StrokeCap.round
    32. ..isAntiAlias = true
    33. ..style = PaintingStyle.fill;
    34. canvas.drawRect(rectBg, paint);
    35. canvas.restore();
    36. // /// 1.生成 ParagraphStyle,可设置文本的基本信息
    37. final paragraphStyle = ui.ParagraphStyle();
    38. /// 2.根据 ParagraphStyle 生成 ParagraphBuilder
    39. final paragraphBuilder = ui.ParagraphBuilder(paragraphStyle);
    40. /// 3.添加样式和文字
    41. paragraphBuilder
    42. ..pushStyle(ui.TextStyle(color: Colors.white, fontSize:110))
    43. ..addText('拍照时间:${DateTools.getCurrentTimeHM()}\n')
    44. ..pushStyle(ui.TextStyle(color: Colors.white, fontSize:90))
    45. ..addText(content)
    46. ;
    47. /// 4.通过 build 取到 Paragraph
    48. final paragraph = paragraphBuilder.build();
    49. /// 5.根据宽高进行布局layout
    50. paragraph.layout(ui.ParagraphConstraints(width: width));
    51. /// 6.绘制
    52. canvas.drawParagraph(paragraph, Offset(100, height-500));
    53. // canvas.restore();
    54. ui.Picture picture = recorder.endRecording();
    55. final img = await picture.toImage(width.toInt(), height.toInt());
    56. final pngBytes = await img.toByteData(format: ui.ImageByteFormat.png);
    57. final Directory directory = await getTemporaryDirectory();
    58. final Directory imageDirectory = await Directory('${directory.path}/image/').create(recursive: true);
    59. String targetPath = imageDirectory.path;
    60. File file = File('${targetPath}watermark${DateTime.now().millisecondsSinceEpoch}.png');
    61. file.writeAsBytesSync(pngBytes!.buffer.asInt8List());
    62. // 图片压缩, 不然iOS端照片会很大, 传不上去
    63. var result = await FlutterImageCompress.compressWithFile(
    64. file.absolute.path,
    65. quality: 95,
    66. rotate: 0,
    67. );
    68. file.writeAsBytesSync(result as List<int>);
    69. return file;
    70. }
    71. }

    Flutter 调用

            File file = await ImageWaterMarkUtils.imageAddWaterMark(image.path, '位置信息:北京市朝阳区三里屯');
    

  • 相关阅读:
    SpringBoot:Thymeleaf模板引擎
    Thinkphp6 配置并使用redis图文详解 小皮面板
    我的四核Cortex-A53学习之路
    暑期JAVA学习(37)定时器
    【云原生 • Kubernetes】kubernetes 核心技术 - Pod
    算法进阶指南图论 通信线路
    【MindSpore易点通】网络构建经验总结中篇
    Tomcat 安装
    游戏缺少dll文件用什么修复?教你多种dll文件修复方法搞定!
    PolarDB-X 全局 Binlog 解读之性能篇(上)
  • 原文地址:https://blog.csdn.net/SHTLoveXX/article/details/108354897