This article covers: screenshots, image saving, real-time canvas drawing based on user gestures.

GitHub address: github.com/yumi0629/Fl…

First, the effect picture:

Demand analysis

The idea was to port a small feature from the project: take a screenshot of the current page, add a doodle feature, and share it with a third-party APP. The sharing function is not discussed at the moment, it can be easily completed by using the plug-in, focusing on screenshot + Doodle + picture saving. The idea is to capture the current screen content, save it to the APP cache directory, doodle the page and then read the file. CustomerPaint is also used to draw in real time according to the user’s gestures, and finally combine the user’s doodle with the original image and save it locally. Watermarking an image is actually a screenshot, because capturing the current screen content is actually a process of converting widgets into byteData and then files.

Take a screenshot and save it

Flutter provides a RepaintBoundaryWidget to capture screenshots. A RepaintBoundary is used to capture the part that needs to be captured. RenderRepaintBoundary can capture the part that needs to be captured. Then, the Image object is converted into UI.Image object through the method of boundary.toimage (), and Image is converted into byteData using image.tobyteData (). Finally, File().writeasBytes () is stored as a File object:

RepaintBoundary(
                  key: _repaintKey,
                  child: Stack(
                    alignment: Alignment.bottomRight,
                    children: <Widget>[
                      Image.asset(
                        'images/food01.jpeg',
                        fit: BoxFit.cover,
                      ),
                      Icon(Icons.translate,),
                    ],
                  ),
                )
Copy the code
RenderRepaintBoundary boundary =
                    _repaintKey.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
File(tempPath).writeAsBytes(pngBytes);
Copy the code

Points to note:

  • Remember to giveRepaintBoundaryAdd akeyBecause we need to get through thiskeyTo find the currentRenderObject, generate aRenderRepaintBoundaryObject;
  • image.toByteData(format: format)Can customize the storage format, general picture ispngFormat, but be careful if yoursRepaintBoundaryThe background color is not set for the part of the package, so the stored picture may have the problem of missing background color.boundary.toImage()Does not automatically add a white background you want, this situation is interceptedText“Is especially obvious.
  • ToImage ({double pixelRatio = 1.0})In thepixelRatioParameters can ostensibly improve image sharpness. Why on the surface? Since this parameter is in pure px units, it has nothing to do with your screen density. For Android, the base width and height of the screen is 360px by 480px, and the default output is 360px by 480px image files. If you willpixelRatioSet it to 10 and your output file size will be 3600px by 4800px, but the screen size of the phone displaying the image will not change. The image will look “clear” as it grows. So, this is the value that I recommendwindow.devicePixelRatio.

Obtain the storage path from Flutter

We can obtain the internal and external storage paths of the APP through the official plugin path_provider:

  • Directory tempDir = await getTemporaryDirectory()Is the same as in iOSNSCachesDirectoryAPI and AndroidgetCacheDirAPI.
  • Directory externalDir = await getExternalStorageDirectory()Is not supported by iOS (UnsupportedError will be raised), which is equivalent to that in AndroidgetExternalStorageDirectoryAPI.
  • Directory applicationDir = await getApplicationDocumentsDirectory()Is the same as in iOSNSDocumentsDirectoryAPI and AndroidAppDataDirectory;

Note external.getexternalstoragedirectory () method, which in most case, it is to access SDCard path, so even in Android calls, also pay attention to the rights issue, it is recommended to use permission_handler plug-in. There is also a good habit when storing files, first check whether the parent directory exists:

void _saveImage(Uint8List uint8List, Directory dir, String fileName) async {
  bool isDirExist = await Directory(dir.path).exists();
  if(! isDirExist) Directory(dir.path).create(); ...... the File (tempPath). WriteAsBytes (uint8List); }Copy the code

graffiti

Collect the user’s gesture path information by wrapping the area to be drawn through the GestureDetector, and draw the path through the canvas.drawline () method:

List<Offset> points = [];

GestureDetector(
          onPanStart: (details) {
          },
          onPanUpdate: (details) {
            RenderBox referenceBox = context.findRenderObject();
            Offset localPosition =
                referenceBox.globalToLocal(details.globalPosition);
            state(() {
              points.add(localPosition);
            });
          },
          onPanEnd: (details) {
          },
        )
Copy the code
for (int i = 0; i < points.length - 1; i++) {
        if(points[i] ! = null && points[i + 1] ! = null) canvas.drawLine(points[i], points[i + 1], _linePaint); }Copy the code

Why not just use the Canvas.drawpoints () method? Even if you set PointMode to pointmode. lines, you’ll notice that the drawn collection of points is not seamless and looks like dashed lines:

Update:
PointMode.polygon



drawLine()



RepaintBoundary