Add watermark to Flutter images, and Flutter saves widgets as images

Inscription
- Holding the sword in the world, starting from your accumulation, wherever you go, you must strive for perfection, that is, toss every day.

important news


There are two implementation ideas for adding a watermark. One is to decode the picture and the watermark, and then mix and re-encode, and the other is to synthesize by Widget.

The implementation idea adopted here is to use the stacked layout Stack to load the image and the watermark part. The watermark part may be a ready-made image or a text and other style components, and then wrap the Stack with the RepaintBoundary component, and then generate it through Widget The function of the picture can achieve the effect of saving the picture as a watermark.

As shown in the figure below, for a picture in the loaded local resource directory, click the check mark in the upper right corner and then combine the picture with the text in the lower right corner to generate a new picture and save it in the mobile phone directory storage.

insert image description here
Then the log entered in the Android Studio console is as follows:

flutter : The saved image address is / Users / androidlongs / Library / Developer / CoreSimulator / Devices / DD736D17 - AB7D - 47 CA - 81 C5 - 2D 5283944F 6 B / data / Containers / Data / Application / AE08A4F2 - 9D FE - 4 A23 - 80 A7 - 7 BEB153A30B0/Documents/723.png _ _ _ _
  • 1

Because the ios simulator is used here, it is saved in the memory space used by the simulator. Open the saved path and view it.
insert image description here
From the saved picture, you can see that the watermark has been added.

Here, the display will encapsulate all the above-mentioned UI functions of display and operation in a StatefulWidget. Here, you can use a new way to open this page. When the displayed picture does not fill the screen, the surrounding area will be dark and translucent, that is, The background of the opened Widet page is transparent, the code is as follows:

/// Jump 
Navigator . of ( context ) 
    . push ( PageRouteBuilder ( 
        opaque :  false , 
        pageBuilder :  ( context , animation , secondaryAnimation )  { 
          return  ImageWatermarkPage ( ) ; 
        } ) ) 
    . then ( ( value )  { 
      if ( value != null ) { 
        /// 
        print( "The saved image address is $value" ) ; 
      }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Public account my big front-end career
For the page ImageWatermarkPage, it realizes the function of displaying pictures and watermarks. The code is as follows:

class ImageWatermarkPage extends StatefulWidget {
  @override
  _RawImageState createState() => _RawImageState();
}

class _RawImageState extends  State < ImageWatermarkPage >  { 
  ///Generate the primary key of the stacked layout Stack of the image 
  GlobalKey _globalKey =  GlobalKey ( ) ; 
  ///Saving 
  bool isSaving =  false ;

  @override 
  Widget  build ( BuildContext context )  { 
    return  Scaffold ( 
      ///The page background is a semi-transparent gray 
      backgroundColor :  Color ( 0x50cdcdcd ) , 
      /// Fill the layout 
      body : Stack ( 
        /// Constraint in the sub-Widget play where the position is not set Alignment 
        alignment :  Alignment . center , 
        children :  [ 
          ///Generate the image and watermark part of the target image 
          buildWaterImageWidget ( ) , 
          /// The operation part of saving the watermark image 
          buildSaveWidget ( ) ,
          ///Part of canceling the operation 
          buildCancleWidget ( ) , 
          /// Progress displayed while the image is being
           saved
 buildLoadingWidget ( ) , 
        ] , 
      ) , 
    ) ; 
  } 
  . . .omit }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

The picture and watermark part of the generated target image are encapsulated in the buildWaterImageWidget method. Here is the UI construction that combines the picture and the watermark part through the cascade layout through the RepaintBoundary component. The code is as follows:

  ///Generate the picture and watermark part of the target image 
  Widget  buildWaterImageWidget ( )  { 
    ///You can create a separate subtree for its child elements 
    ///It is equivalent to a small branch on the total tree Widgets 
    return  RepaintBoundary ( 
      key : _globalKey , 
      /// Widget for generating images 
      child :  Container ( 
        /// full screen 
        width :  MediaQuery . of ( context ) . size . width , 
        height :  MediaQuery . of ( context ) . size .height,
        child: Stack(
          children: [
            Image.asset(
              "assets/images/2.0x/s03.jpeg",
              fit: BoxFit.fill,
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
            ), 
            /// The watermark part in the lower right corner 
            Positioned ( 
              bottom :  20 , right :  20 , 
              child :  Container ( 
                padding :  EdgeInsets . only ( left :  8 , right :  8 , top :  2 , bottom :  2 ) , 
                decoration : 
                BoxDecoration ( border :  Border . all (
                    color :  Colors . red , 
                    width :  1.0 
                ) ) , 
                child :  Text ( "This is the watermark" , style :  TextStyle ( fontSize :  14 , color :  Colors . white ) , ) , 
              ) , 
            ) 
          ] , 
        ) , 
      ) , 
    ) ; 
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

For the method buildSaveWidget, it is a method that encapsulates the build save checkmark and function. The code is as follows:

 
  ///The operation part of saving the watermark image 
  Widget  buildSaveWidget ( )  { 
    ///The small check button is displayed in the upper right corner 
    return  Positioned ( 
      top :  40 , 
      right :  20 , 
      child :  IconButton ( 
        icon :  Icon ( Icons . check_circle , color :  Colors . blue , size :  33 , ) , 
        onPressed :  ( ) async {
          ///Update page display 
          setState ( ( )  { 
            isSaving =  true ; 
          } ) ; 
          /// Save 
          Widget as ui.Image ui.Image _image = await ImageLoaderUtils.imageLoader.getImageFromWidget ( _globalKey
               ) through globalkey ;

          ///Asynchronously save this image in the internal storage directory of the phone 
          String localImagePath =   await ImageUtils . imageUtils
               . saveImageByUIImage ( _image , isEncode :  false ) ; 
          /// After saving, close the current page and return the saved image path to the above A Page 
          Navigator . of ( context ) . pop ( localImagePath ) ; 
        } , 
      ) , 
    ) ; 
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

The part that cancels the operation is the close button in the upper left corner of the page. The function is relatively simple to build in the buildCancleWidget method. The code is as follows:


  ///Part of the 
  Widget that cancels the operation  buildCancleWidget ( )  { 
    return   Positioned ( 
      top :  40 , 
      left :  20 , 
      child :  IconButton ( 
        icon :  Icon ( Icons . clear , color :  Colors . red , size :  33 , ) , 
        onPressed :  ( )  { 
          Navigator . of (context).pop();
        },
      ),
    );
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

The buildLoadingWidget method encapsulates that when the user clicks save, the value of isSaving is updated to true, and then a small circle is displayed on the page to give the user a prompt feedback about the operation. The code is as follows:

  ///Progress displayed while the image is being saved 
  ///A small circle 
  widget  buildLoadingWidget ( )  { 
    return isSaving ? CircularProgressIndicator ( ) : Container ( ) ; 
  }
  • 1
  • 2
  • 3
  • 4
  • 5

Here, the method of saving the Widget as an image, the getImageFromWidget method of ImageLoaderUtils, and the method saveImageByUIImage of saving the Image to the phone storage are as follows:

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';

/// Image loading tool 
class  ImageLoaderUtils  { 
  //Privately construct 
  ImageLoaderUtils . _ ( ) ; 
  // singleton mode creates 
  static  final  ImageLoaderUtils imageLoader =  ImageLoaderUtils . _ ( ) ;


  // Convert a Widget to an image.Image object 
  Future < ui . Image >  getImageFromWidget ( GlobalKey globalKey ) async { 
    // globalKey is the key of the widget that needs to be imaged 
    RenderRepaintBoundary boundary = globalKey . currentContext . findRenderObject ( ) ; 
    // Convert to image 
    ui . Image img = await boundary . toImage ( ) ; 
    return img ; 
  }

 ///Save the specified file to the directory space. 
  ///[image] Here is the Image under the ui package used 
  ///[picName] Save to the local file (picture) file name, such as test_image 
  ///[endFormat] Save to the local file (picture) file format , such as png, 
  ///[isReplace] When a file (picture) with the same name exists locally, true is to replace 
  ///[isEncode] to encode the saved file (picture) 
  /// Finally save it to the local file (picture) ) is named picName.endFormat 
  Future < String >  saveImageByUIImage ( ui . Image image , 
      { String picName , 
      String endFormat =  "png" , 
      bool isReplace =  true , 
      bool isEncode =  true } ) async{ 
    ///Get the local disk path 
    /*
     * Obtained in the Android platform is /data/user/0/com.studyyoun.flutterbookcode/app_flutter
     * This method gets the Documents path on the iOS platform
     */
    Directory appDocDir = await getApplicationDocumentsDirectory();
    String appDocPath = appDocDir.path;

    ///Splicing directory 
    if  ( picName ==  null  || picName . trim ( ) . length ==  0 )  { 
      /// When the user does not specify picName, take the current time name 
      picName =  "${DateTime.now() .millisecond.toString()}.$endFormat" ; 
    }  else  { 
      picName =  "$picName.$endFormat" ; 
    }

    if  ( isEncode )  { 
      ///Encrypt the saved image name 
      picName = md5 . convert ( utf8 . encode ( picName ) ) . toString ( ) ; 
    }

    appDocPath = "$appDocPath/$picName";

    ///Check if the image exists 
    var file =  File ( appDocPath ) ; 
    bool exist = await file . exists ( ) ; 
    if  ( exist )  { 
      if  ( isReplace )  { 
        ///If the image exists, delete and replace 
        ///If If the new image fails to load, the old image is also deleted 
        await file . delete ( ) ; 
      }  else  { 
        ///If the image exists, it will not be downloaded 
        return  null ; 
      } 
    } 
    ByteData byteData = await image. toByteData ( format :  ImageByteFormat . png ) ; 
    Uint8List pngBytes = byteData . buffer . asUint8List ( ) ; 
    print ( "Saved image path $appDocPath" ) ;

    ///Save the data format of Uint8List 
    await File ( appDocPath ) . writeAsBytes ( pngBytes ) ;

    return appDocPath;
  }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

The path_provider is used here to get the storage directory of the phone, so you need to add dependencies:

  #Get the phone storage directory
  path_provider: ^1.6.9
  • 1
  • 2

The latest version of path_provider can be viewed in the pub China warehouse. Click here to view

Tags: Add watermark to Flutter images, and Flutter saves widgets as images

A full set of tutorials for Flutter project development flutter image watermark flutter save image widget save as image

Related: Add watermark to Flutter images, and Flutter saves widgets as images