การใช้ Quadratic Curve เพื่อ Mask ภาพ Onboarding ใน Flutter

วันนี้ลองฝึกเขียน Flutter โดยทำความเข้าใจเรื่องของ ClipPath() ซึ่งมีความน่าสนใจตรงที่ เราสามารถเขียน Code วาดเส้นให้แสดงภาพตามที่เรากำหนดให้มีความโค้ง ความเว้าในรูปทรงต่างๆ…

โดยที่ภาพก็เป็น Original หลักการคล้ายๆกับการ Mask ใน Photoshop เลยทีเดียว เพื่อให้เกิดแรงบันดาลใจขออนุญาตใช้ภาพน้อง Ink Waruntorn มาใช้เป็นแรงบันดาลใจในการศึกษาครับ ^^

Quadratic Curve

จะเห็นว่าถ้าเรากำหนดจุด เส้นที่วาดจะเป็นเส้นตรง (Linear) ดังนั้นถ้าเราต้องการสร้าง Curve หรือเส้นโค้งบนภาพ สังเกตุจะมี 3 จุด คือ P0, P1, P2 ถึงจะสร้างเส้นโค้งได้ สามารถใช้ทฤษฏีของ Quadratic Bezier (Wiki) ซึ่งใน Flutter มีให้ใช้งาน

    // P0 = (x0, y0)
    double px0 = 0.0;
    double py0 = size.height - 20;
    // P1 = (x1, y1)
    double px1 = size.width / 4; 
    double py1 = size.height;
    // P2 = (x2, y2)
    double px2 = size.width / 2.25; 
    double py2 = size.height - 30;

สร้างเส้นโค้งที่

    // Make curve1
    var CP1 = new Offset(px1, py1);
    var EP1 = new Offset(px2, py2);
    path.quadraticBezierTo(CP1.dx, CP1.dy, EP1.dx, EP1.dy);
// P(x3, y3)
double px3 = size.width - (size.width / 3.25);
double py3 = size.height - 60;
// P(x4, y4)
double px4 = size.width;
double py4 = size.height - 40;

สร้างเส้นโค้งที่ 2

// Make curve2
var CP2 = new Offset(px3, py3);
var EP2 = new Offset(px4, py4);
path.quadraticBezierTo(CP2.dx, CP2.dy, EP2.dx, EP2.dy);

เสร็จแล้วลากเส้นกลับไปที่ y0 ไม่งั้นภาพจะแหว่ง (เหมือนเล่นดึงเชือกเลย)

// Draw line up to y0
path.lineTo(size.width, 0);
path.close();

ทีนี้มาดู Code เต็มๆ ของ Class ที่เราทำภาพสำหรับ On boarding กัน

import 'package:flutter/material.dart';

class OnBoardingImageClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path();
    // (x0, y0)
    double px0 = 0.0;
    double py0 = size.height - 20;
    path.lineTo(px0, py0);

    // P(x1, y1)
    double px1 = size.width / 4;
    double py1 = size.height;

    // P(x2, y2)
    double px2 = size.width / 2.25;
    double py2 = size.height - 30;

    // Make curve1
    var CP1 = new Offset(px1, py1);
    var EP1 = new Offset(px2, py2);
    path.quadraticBezierTo(CP1.dx, CP1.dy, EP1.dx, EP1.dy);

    // P(x3, y3)
    double px3 = size.width - (size.width / 3.25);
    double py3 = size.height - 60;

    // P(x4, y4)
    double px4 = size.width;
    double py4 = size.height - 40;

    // Make curve2
    var CP2 = new Offset(px3, py3);
    var EP2 = new Offset(px4, py4);
    path.quadraticBezierTo(CP2.dx, CP2.dy, EP2.dx, EP2.dy);

    // Draw line up to y0
    path.lineTo(size.width, 0);
    path.close();

    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

ผลลัพธ์ที่ได้เป็นเส้นโค้งสวยงาม

อีกตัวอย่างหนึ่ง

import 'package:flutter/material.dart';

class OnBoardingImageClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path();
    // Start (0, 0)
    path.moveTo(0, 0);

    // Draw a line to width = 0, height = Image height
    // P0 (x0, y0)
    path.lineTo(0, size.height);

    // Control point Q0 = (CP1.dx,CP1.dy) --> End point Q1 =
    // P1 (x1, y1)
    double px1 = 12;
    double py1 = size.height - 38;
    double px2 = 40;
    double py2 = size.height - 48;

    var CP1 = new Offset(px1, py1);
    var EP1 = new Offset(px2, py2);
    path.quadraticBezierTo(CP1.dx, CP1.dy, EP1.dx, EP1.dy);

    // Draw a line to size.width - 40, size.height - 140
    // P2 (x2, y2)
    double px3 = size.width - 40;
    double py3 = size.height - 140;
    path.lineTo(px3, py3);

    // Control point Q2 = (CP2.dx,CP2.dy) --> End point Q3 =(EP2.dx, EP2.dy)
    // P3 (x3, y3)
    double px4 = size.width;
    double py4 = size.height - 145;
    double px5 = size.width;
    double py5 = size.height - 212;
    var CP2 = new Offset(px4, py4);
    var EP2 = new Offset(px5, py5);
    path.quadraticBezierTo(CP2.dx, CP2.dy, EP2.dx, EP2.dy);

    path.lineTo(size.width, 0);

    path.close();

    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

เราสามารถปรับ Code ให้กระชับและสั้นขึ้นได้ดังนี้

import 'package:flutter/material.dart';

class OnBoardingImageClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path();
    // Start (0, 0)
    path.moveTo(0, 0);

    // Draw a line to width = 0, height = Image height
    path.lineTo(0, size.height);

    // Control point (CP1.dx,CP1.dy) --> End point (EP1.dx, EP1.dy)
    path.quadraticBezierTo(12, size.height - 38, 40, size.height - 48);

    // Draw a line to size.width - 40, size.height - 140
    path.lineTo(size.width - 40, size.height - 140);

    // Control point (CP2.dx,CP2.dy) --> End point (EP2.dx, EP2.dy)
    path.quadraticBezierTo(
        size.width, size.height - 145, size.width, size.height - 212);

    path.lineTo(size.width, 0);

    path.close();

    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}