swift 截屏、录屏监听、添加水印

如果系统未禁止应用截屏,导致应用的关键信息可能会被截屏泄露,通过查看应用截图,可能获取到用户的敏感信息,造成个人资料外泄。
iOS实现不了不让截屏或者录屏,但是提供的截屏或者录屏的监听方法,当用户截屏或录屏时系统会发送相关通知,我们可以提示用户截屏或录屏会泄露一些个人安全信息,类似于微信或支付宝的付款码截屏。

监听通知

监听截屏通知
NotificationCenter.default.addObserver(self, selector: #selector(screenshots), name: UIApplication.userDidTakeScreenshotNotification, object: nil)

监听屏幕状态通知
NotificationCenter.default.addObserver(self, selector: #selector(screenCaptured), name: UIScreen.capturedDidChangeNotification, object: nil)

捕获屏幕状态

// 监听录屏通知,iOS 11后才有录屏
if #available(iOS 11.0, *) {
    // 如果正在捕获此屏幕(例如,录制、空中播放、镜像等),则为真
    if UIScreen.main.isCaptured {
       screenCaptured()
    }
}

提示方法

@objc func screenCaptured(){
     let alertVC = UIAlertController(title: "提示", message: "为保证用户名,密码安全,请不要截屏或录屏!", preferredStyle: .alert)
     let action = UIAlertAction(title: "知道了", style: .default, handler:nil)
     alertVC.addAction(action)
     self.present(alertVC, animated: true, completion: nil);
        
    }

移除监听

deinit {
     NotificationCenter.default.removeObserver(self, name: UIApplication.userDidTakeScreenshotNotification, object: nil);
     NotificationCenter.default.removeObserver(self, name: UIScreen.capturedDidChangeNotification, object: nil);
        
}

添加水印

func createWaterMarkFor(Text strTxt:String,
                            andFullSize fsize:CGSize,
                            andCorners corners:UIRectCorner? = nil,
                        withRadius r:CGFloat? = nil) -> UIImage {
    
    //[S] 1、设置水印样式
    let paragraphStyle:NSMutableParagraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineBreakMode = .byWordWrapping
    paragraphStyle.lineSpacing = 6.0 * 10
    paragraphStyle.alignment = .center
    
    var _attr:[NSAttributedString.Key:Any] = [
        .font : UIFont.systemFont(ofSize: 15, weight: .semibold),
            .foregroundColor:UIColor.init(red: 222/255.0, green: 222/255.0, blue: 222/255.0, alpha: 0.8),
        .paragraphStyle: paragraphStyle,
        .kern:1.0,
    ]
    
    if #available(iOS 14.0, *) {
        _attr[.tracking] = 1.0
    }
    
    let attributedString:NSMutableAttributedString = NSMutableAttributedString.init(string: strTxt)
    let stringRange = NSMakeRange(0, attributedString.string.utf16.count)
    attributedString.addAttributes(_attr,range: stringRange)
    //[E]
    
    //[S] 2、建立水印图
    let _max_value = attributedString.size().width > attributedString.size().height ? attributedString.size().width : attributedString.size().height
    let _size = CGSize.init(width: _max_value + 10, height: _max_value + 10)
    
    //2.1、设置上下文
    if UIScreen.main.scale > 1.5 {
        UIGraphicsBeginImageContextWithOptions(_size,false,0)
    }
    else{
        UIGraphicsBeginImageContext(_size)
    }
    var context = UIGraphicsGetCurrentContext()
    
    //2.2、根据中心开启旋转上下文矩阵
    //将绘制原点(0,0)调整到源image的中心
    context?.concatenate(.init(translationX: _size.width * 0.8, y: _size.height * 0.4))
    
    //以绘制原点为中心旋转45°
    context?.concatenate(.init(rotationAngle: -0.25 * .pi))
    
    //将绘制原点恢复初始值,保证context中心点和image中心点处在一个点(当前context已经发生旋转,绘制出的任何layer都是倾斜的)
    context?.concatenate(.init(translationX: -_size.width * 0.8, y: -_size.height * 0.4))
    
    //2.3、添加水印文本
    attributedString.draw(in: .init(origin: .zero, size: _size))
    
    //2.4、从上下文中获取水印图
    let _waterImg = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage.init()
    //[E]
    
    //[S] 3、重设上下文,建立底图
    if UIScreen.main.scale > 1.5 {
        UIGraphicsBeginImageContextWithOptions(fsize,false,0)
    }
    else{
        UIGraphicsBeginImageContext(fsize)
    }
    context = UIGraphicsGetCurrentContext()
    
    //3.1圆角底图(可选)
    if corners != nil && r != nil && r?.isNaN == false && r?.isFinite != false {
        let rect:CGRect = .init(origin: .zero, size: fsize)
        let bezierPath:UIBezierPath = UIBezierPath.init(roundedRect: rect,
                                                        byRoundingCorners: corners!,
                                                        cornerRadii: CGSize(width: r!, height: r!))
        
        context?.addPath(bezierPath.cgPath)
    }
    
    //3.2 将水印图贴上去
    var _tempC = fsize.width / _waterImg.size.width
    var _maxColumn:Int = _tempC.isNaN || !_tempC.isFinite ? 1 : Int(_tempC)
    if fsize.width.truncatingRemainder(dividingBy: _waterImg.size.width) != 0 {
        _maxColumn += 1
    }
    
    _tempC = fsize.height / _waterImg.size.height
    var _maxRows:Int = _tempC.isNaN || !_tempC.isFinite ? 1 : Int(_tempC)
    if fsize.height.truncatingRemainder(dividingBy: _waterImg.size.height) != 0 {
        _maxRows += 1
    }
    
    for r in 0..<_maxRows {
        for c in 0..<_maxColumn {
            let _rect:CGRect = .init(origin: .init(x: CGFloat(c) * _waterImg.size.width,
                                                   y: CGFloat(r) * _waterImg.size.height),
                                     size: _waterImg.size)
            _waterImg.draw(in: _rect)
        }
    }
    
    //裁剪、透明
    context?.clip()
    context?.setFillColor(UIColor.clear.cgColor)
    context?.fill(.init(origin: .zero, size: fsize))
    
    //3.3 输出最终图形
    let _canvasImg = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage.init()
    //[E]
    
    //4、关闭图形上下文
    UIGraphicsEndImageContext()
    
    return _canvasImg
    
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
实现 Android 录屏添加水印的一种方法是使用 MediaProjection API 和 Canvas 绘图。以下是实现的步骤: 1. 获取 MediaProjection 对象,在 Android 5.0 及以上版本中,需要用户授权获取 MediaProjection 对象,可以使用 startActivityForResult() 方法获取。 2. 创建 VirtualDisplay 对象,将 MediaProjection 对象传入,设置录制的屏幕宽高和 dpi。 3. 创建 Surface 对象,将 VirtualDisplay 对象的 surface 传入。 4. 在 Surface 上绘制屏幕内容,可以使用 Canvas 绘图实现。 5. 绘制水印,可以使用 Bitmap、Paint、Canvas 等方法实现。 6. 将绘制好的帧数据传入 MediaCodec 编码成视频数据。 7. 最后将视频数据保存到文件或者上传到服务器。 以下是示例代码: ```java // 获取 MediaProjection 对象 private void startScreenCapture() { MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE); startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION); } // 获取 MediaProjection 授权结果 @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == REQUEST_MEDIA_PROJECTION) { if (resultCode == RESULT_OK) { // 创建 VirtualDisplay 对象 mediaProjection = ((MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE)).getMediaProjection(resultCode, data); virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture", screenWidth, screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null); } } } // 在 Surface 上绘制屏幕内容和水印 private void drawOnSurface(Canvas canvas) { // 绘制屏幕内容 canvas.drawBitmap(screenBitmap, 0, 0, null); // 绘制水印 Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setTextSize(24); paint.setAntiAlias(true); canvas.drawText("Watermark", 10, 30, paint); } // 将帧数据传入 MediaCodec 编码成视频数据 private void encodeFrame() { try { mediaCodec = MediaCodec.createEncoderByType("video/avc"); MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", screenWidth, screenHeight); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); surface = mediaCodec.createInputSurface(); mediaCodec.start(); ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); boolean isEOS = false; while (!isEOS) { int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { long presentationTimeUs = System.nanoTime() / 1000; Bitmap bitmap = getBitmapFromSurface(); if (bitmap != null) { Canvas canvas = surface.lockCanvas(null); drawOnSurface(canvas); surface.unlockCanvasAndPost(canvas); } ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(screenBuffer); if (isEOS) { mediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM); } else { mediaCodec.queueInputBuffer(inputBufferIndex, 0, screenBuffer.length, presentationTimeUs, 0); } int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) { configByte = new byte[bufferInfo.size]; configByte = outData; } else if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME) { byte[] keyframe = new byte[bufferInfo.size + configByte.length]; System.arraycopy(configByte, 0, keyframe, 0, configByte.length); System.arraycopy(outData, 0, keyframe, configByte.length, outData.length); outData = keyframe; } if (outData != null) { // 保存视频数据 } mediaCodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); } } } } catch (Exception e) { e.printStackTrace(); } } // 从 Surface 中获取 Bitmap private Bitmap getBitmapFromSurface() { Bitmap bitmap = null; try { bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888); surface.lockCanvas(null).drawBitmap(bitmap, 0, 0, null); surface.unlockCanvasAndPost(canvas); } catch (Exception e) { e.printStackTrace(); } return bitmap; } ``` 需要注意的是,由于 Android 中录屏功能和实现方式在不同的版本中有所不同,因此上述代码仅供参考,实际应用中可能需要根据具体情况进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张子都

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值