Core Graphics Tutorial - Part 1

Core graphics tên gọi khác “Quartz”, là 1 trong những framework lâu đời nhất trên ios cung cấp các API liên quan đến đồ hoạ như: vẽ các hình thoi tròn vuông, fill màu vẽ bóng, xử lý hình ảnh, hoặc dùng transform view quay góc, zoom to nhỏ, tạo ảnh, các bộ lọc ảnh, hay thậm chí tạo các file pdf từ các thành phần như lable, view, image ...

1. Các kiểu dữ liệu cơ bản trong Core Graphics

1.1 CGPoint

Là kiểu dữ liệu gồm 2 tham số x, y của trục toạ độ

let point1 = CGPoint(x: 0.0, y: 0.0)
let point2 = CGPointMake(0.0, 0.0)

1.2 CGSize

Là kiểu dữ liệu gồm chiều dài, chiều rộng

let size1 = CGSize(width: 100.0, height: 100.0)
let size2 = CGSizeMake(100, 100)

1.3 CGRect

Là kiểu dữ liệu chứa cả CGPoint và CGSize, nó xác định vị trí và kích thước của đối tượng mà ta muốn vẽ.

let rect1 = CGRect(origin: point1, size: size1)
let rect2 = CGRectMake(0.0, 0.0, 100.0, 100.0)

1.4 Point vs Pixel

1.4.1 Point

Khái niệm point khá trừu tượng, nó liên quan đến các framework dùng để vẽ trong ios như Quartz, UIKit, và Core Animation.

1.4.2 Pixel

pixel thì khá cụ thể, nó là mật độ điểm ảnh trên màn hình device. 1 point không phải lúc nào cũng tương ứng 1 pixel, trên các iphone đời đầu thì tỉ lệ là 1:1 còn trên iphone retina(5 5s 6 6s) thì tỉ lệ là 1:2, trên iphone 7+ tỉ lệ là 1:3. Điều này có nghĩa là nếu trên 1device có độ phân giải thường 1 line độ rộng 1 point sẽ chiếm 1 pixel, thì trên device có độ phân giải cao nó sẽ chiếm 2 pixel nhưng chung cho cả 2 loại device line đều có chung kích thước.

2. CGContext

Cách core graphics cho ra các output cần thiết được gọi chung là render, render ở đây có thể là vẽ 1 cái gì đó trên uiview, vẽ 1 cái ảnh hay thậm chí là sinh ra 1 file pdf. Tất cả các đối tượng mà core graphics dùng trong quá trình render được gọi chung là context, context chứa tất cả các kiểu dữ liệu và các hàm core graphics dùng trong quá trình render. Có rất nhiều các context khác nhau, nếu chúng ta muốn sử dụng core graphics để vẽ trên view chúng ta sẽ cần context để vẽ các đường, điểm, hình dạng khác nhau. Còn nếu chúng ta muốn tạo file pdf chúng ta sẽ cần đến pdf context.

2.1 CGPath

1 trong những nguyên lý hoạt động cơ bản trong core graphics là tạo path. Path chính là miêu tả dạng toán học của tất cả các hình dạng vật thể mà chúng ta muốn vẽ:

  • Hình tròn:
  • Hình vuông :
  • Hay thậm chí phức tạp hơn rất nhiều :

Như chúng ta thấy hình vẽ ở trên rất phức tạp, nó được vẽ theo nhiều màu khác nhau, các đường vẽ có thể cong tròn, có bo góc hoặc không. Nếu đã api core graphics bạn sẽ thấy không có những hàm kiểu như:

CGStrokePath (path, fillColor, strokeColor, lineWidth)

Thay vào đó chỉ có hàm:

CGContextStrokePath(context)

vậy thì core graphics lấy những thông số để vẽ ở đâu ? đó chính là context context giống như 1 biến global nắm giữ tất cả thông tin cần thiết của core graphics

  • fill color.
  • stroke color.
  • line width
  • alpha
  • transformation
  • text attribute ....

Như chúng ta thấy thì các thông số của path hoàn toàn không được đưa vào ở đây, chúng đã được lưu trong context. Bất kể khi nào chúng ta dùng core graphics để vẽ nó sẽ tìm trong context hiện tại những thông tin cần thiết, do đó cùng 1 cách gọi vẽ nhưng có thể cho ra những kết quả khác nhau tuỳ thuộc vào giá trị mà context hiện tại đang nắm giữ. Có thể nói đây là vừa 1 tính năng khá hay của core graphics nhưng bên cạnh đó nó cũng rất dễ gây ra nhưng lỗi ngớ ngẩn không mong muốn chút nào. Ví dụ: ta có 1 hàm vẽ hình chữ nhập màu cam

func drawOrangeSquare() {
    CGContextSetStrokeColorWithColor(context, UIColor.orangeColor())
    CGContextAddRect(context, CGRect(origin: origin, size: size))
}

Giờ ta lại có tiếp 1 hàm vẽ hình tròn màu đỏ

func drawRedCircle() {
    CGContextSetStrokeColorWithColor(context, UIColor.redColor())
    CGContextAddEllipseInRect(context, CGRect(origin: origin, size: size))
}

Chúng ta sẽ kết hợp nó vào hàm đầu tiên:

func drawOrangeSquare() {
    CGContextSetStrokeColorWithColor(context, UIColor.orangeColor())
    drawRedCircle()
    CGContextAddRect(context, CGRect(origin: origin, size: size))
}

Kết quả chúng ta đc 1 hình tròn + chữ nhật màu đỏ thay vì hình chữ nhập màu cam như mong đợi

Để tránh bị lỗi như vậy chúng ta có thể dùng hàm lưu lại trạng thái hiện tại context -> draw -> restore lại tráng thái trước đó của context

func drawOrangeSquare() {
    CGContextSetStrokeColorWithColor(context, UIColor.orangeColor())
    CGContextSaveGState (currentContext)
    drawRedCircle()
    CGContextRestoreGState (currentContext)
    CGContextAddRect(context, CGRect(origin: origin, size: size))
}

Vậy là chúng ta đã vẽ được 1 hình tròn màu đỏ và 1 hình chữ nhập màu cam.

3. 1 số hàm vẽ cơ bản của CGContext

3.1 CGContextSetLineCap

Xác định kiểu vẽ điểm kết thúc của đường thẳng, có 3 kiểu trong CGContextSetLineCap

  • Butt
  • Round
  • Square Trong đó 2 kiểu Butt và Square thì cho ra hình dạng điểm kết thúc là vuông, chúng khác nhau ở độ dài của line, Square làm cho line trong dài hơn so với Butt. Còn Round làm cho điểm kết thúc của line trông tròn hơn.

CGContextSetLineCap(context, CGLineCap.Round)

3.2 CGContextSetLineWidth

Set độ rộng cho cgcontext, nếu chúng ta vẽ 1 hình nào đó, nó chính là độ rộng viền của hình đó.

CGContextSetLineWidth(context, self.strokeWidth)

3.3 CGContextSetStrokeColorWithColor

Set màu cho cgcontext khi vẽ.

CGContextSetStrokeColorWithColor(context, self.strokeColor.CGColor)

3.4 Vẽ đường thẳng

Để vẽ 1 đường thẳng chúng ta dùng 2 hàm

func CGContextMoveToPoint(c: CGContext?, _ x: CGFloat, _ y: CGFloat)
  • CGContextMoveToPoint: sẽ di chuyển đến điểm đầu của đường thẳng
func CGContextAddLineToPoint(c: CGContext?, _ x: CGFloat, _ y: CGFloat)
  • CGContextAddLineToPoint: sẽ thực hiện vẽ 1 đường thằng từ điểm bắt đầu đến điểm kết thúc
CGContextMoveToPoint(context, startPoint.x, startPoint.y)
CGContextAddLineToPoint(context, endPoint.x, endPoint.y)

3.5 Vẽ hình chữ nhật (hình vuông)

Để vẽ hình chữ nhật chúng ta dùng hàm

func CGContextAddRect(c: CGContext?, _ rect: CGRect)
  • Rect: bao gồm 1 origin point chứa điểm bắt đắt đầu và 1 size chứ kích thước của hình chữ nhật, nếu width mà bằng height chúng ta sẽ có hình vuông.
let origin = CGPointMake(min(startPoint.x, endPoint.x), min(startPoint.y, endPoint.y))
let size = CGSizeMake(abs(endPoint.x - startPoint.x), abs(endPoint.y - startPoint.y))
CGContextAddRect(context, CGRect(origin: origin, size: size))

3.6 Vẽ hình eclipse

Để vẽ đc hình eclipse chúng ta dùng hàm

func CGContextAddEllipseInRect(c: CGContext?, _ rect: CGRect)
  • Rect: bao gồm 1 origin point và 1 CGSize
let origin = CGPointMake(min(startPoint.x, endPoint.x), min(startPoint.y, endPoint.y))
let size = CGSizeMake(abs(endPoint.x - startPoint.x), abs(endPoint.y - startPoint.y))
CGContextAddEllipseInRect(context, CGRect(origin: origin, size: size))

3.7 Vẽ hình tròn

Để vẽ hình tròn ta dùng hàm

func CGContextAddArc(c: CGContext?, _ x: CGFloat, _ y: CGFloat, _ radius: CGFloat, _ startAngle: CGFloat, _ endAngle: CGFloat, _ clockwise: Int32)
  • x, y là toạ độ của tâm đường tròn
  • radius: bán kính đường tròn
  • startAngle: góc bắt đầu để vẽ đường tròn
  • endAngle: góc kết thúc để vẽ đường tròn
var radius = 0.0
var centerX = abs(endPoint.x - startPoint.x) / 2
var centerY = abs(endPoint.y - startPoint.y) / 2
centerX += (startPoint.x < endPoint.x) ? startPoint.x : endPoint.x
centerY += (startPoint.y < endPoint.y) ? startPoint.y : endPoint.y
radius = sqrt(pow(Double(endPoint.x) - Double(startPoint.x), 2.0) + pow(Double(endPoint.y) - Double(startPoint.y), 2.0)) / 2
CGContextAddArc(context, centerX, centerY, CGFloat(radius), 0, CGFloat(M_PI * 2), 0)

Nếu chúng ta bắt đầu vẽ từ góc 0 và kết thúc ở 2Pi thì ta sẽ được 1 vòng tròn hoàn chỉnh

Còn nếu ta để góc kết thúc ở Pi ta sẽ được nửa vòng tròn

4. Demo

https://github.com/pqhuy87it/MonthlyReport/tree/master/CoreGraphicsPart1