NSAffineTransform là một lớp từ AppKit (macOS) biểu diễn phép biến đổi affine trong đồ họa hai chiều. Nó đóng gói một ma trận 3×3 mô tả sự kết hợp của phép tịnh tiến, tỷ lệ, xoay và cắt của tọa độ. Theo Tài liệu Apple (2026), NSAffineTransform được sử dụng rộng rãi trong Core Graphics để biến đổi đường dẫn, điểm và ngữ cảnh đồ họa trên macOS.
Những điểm chính
NSAffineTransform là một lớp từ framework AppKit, chỉ khả dụng trên macOS. Nó biểu diễn một phép biến đổi affine trong không gian hai chiều thông qua ma trận 3×3. Không giống như CGAffineTransform từ Core Graphics (có sẵn trên iOS), NSAffineTransform là một wrapper hướng đối tượng bao quanh cấu trúc CGAffineTransform, thêm các phương thức tiện lợi để làm việc với NSBezierPath, NSPoint và NSSize.
Phép biến đổi affine là một phép biến đổi của mặt phẳng bảo toàn mối quan hệ “điểm nằm trên đường thẳng” và tính song song của các đường. Nói cách khác, sau phép biến đổi affine, các đường song song vẫn song song và các đường thẳng vẫn thẳng. Tuy nhiên, khoảng cách, góc và diện tích có thể thay đổi: tỷ lệ, xoay và cắt là các trường hợp đặc biệt của phép biến đổi affine.
Trên iOS, tương đương với NSAffineTransform là cấu trúc CGAffineTransform từ Core Graphics. Cả hai API đều dựa trên cùng một mô hình toán học — ma trận biến đổi affine 3×3 với hàng cuối cùng luôn là [0, 0, 1]. Sự khác biệt là NSAffineTransform là một lớp (với retain/release trong Objective-C), trong khi CGAffineTransform là một cấu trúc (kiểu giá trị) trong Swift và C.
NSAffineTransform hỗ trợ bốn loại biến đổi cơ bản: translate (tịnh tiến), scale (tỷ lệ), rotate (xoay) và shear (cắt). Mỗi loại được biểu diễn bằng một phương thức riêng và có thể được áp dụng cho một đối tượng biến đổi hiện có hoặc được sử dụng để tạo một đối tượng mới.
| Thao tác | Phương thức NSAffineTransform | Tham số | Hiệu ứng trực quan |
|---|---|---|---|
| Translate | translateXBy:yBy: | deltaX, deltaY | Di chuyển đối tượng dọc theo trục X và Y |
| Scale | scaleBy: / scaleXBy:yBy: | factor / scaleX, scaleY | Phóng to hoặc thu nhỏ kích thước |
| Rotate | rotateByDegrees: / rotateByRadians: | góc tính bằng độ hoặc radian | Xoay quanh gốc tọa độ |
| Shear | Thông qua setTransformStruct | thành phần ma trận m12, m21 | Nghiêng đối tượng dọc theo trục |
Translate thêm một độ dời vào tọa độ của tất cả các điểm của đối tượng. Tham số deltaX và deltaY xác định lượng dịch chuyển ngang và dọc tương ứng. Giá trị deltaX dương dịch sang phải, deltaY dương dịch lên trên. Translate không thay đổi hình dạng hoặc kích thước của đối tượng — chỉ thay đổi vị trí của nó.
Scale nhân tọa độ của các điểm với một hệ số cho trước. Với tỷ lệ đồng nhất (một tham số), đối tượng phóng to hoặc thu nhỏ theo tỷ lệ. Với tỷ lệ không đồng nhất, hình dạng bị méo. Hệ số 1.0 giữ nguyên kích thước, lớn hơn 1.0 phóng to, từ 0.0 đến 1.0 thu nhỏ. Giá trị âm tạo ra phản chiếu gương.
Rotate xoay đối tượng quanh gốc tọa độ một góc cho trước. Góc xoay dương tương ứng với xoay ngược chiều kim đồng hồ. rotateByDegrees chấp nhận độ (0–360), rotateByRadians chấp nhận radian (0–2π). Xoay luôn được thực hiện so với điểm (0, 0), vì vậy để xoay quanh một điểm bất kỳ, cần kết hợp rotate với translate.
Phép biến đổi affine trong NSAffineTransform được biểu diễn bằng ma trận 3×3 với hàng cuối cùng luôn là [0, 0, 1]. Đây là biểu diễn toán học tiêu chuẩn cho phép biểu diễn sự kết hợp của các biến đổi dưới dạng tích của ma trận. Cấu trúc ma trận được định nghĩa thông qua NSAffineTransformStruct, chứa sáu thành phần quan trọng: m11, m12, m21, m22, tX, tY.
Các thành phần m11, m12, m21, m22 chịu trách nhiệm cho tỷ lệ, xoay và cắt. Các thành phần tX và tY chịu trách nhiệm cho tịnh tiến. Trong cấu hình tiêu chuẩn (ma trận đơn vị), m11 = 1, m22 = 1, và các thành phần còn lại bằng 0. Để lấy giá trị hiện tại của ma trận, sử dụng transformStruct — thuộc tính của NSAffineTransform trả về cấu trúc NSAffineTransformStruct.
Phép nhân ma trận là một phép toán không giao hoán. Điều này có nghĩa là thứ tự áp dụng các biến đổi rất quan trọng: translate + rotate cho kết quả khác với rotate + translate. Khi kết hợp, NSAffineTransform nhân ma trận mới ở bên trái với ma trận hiện tại: newMatrix = operation × currentMatrix. Do đó, biến đổi được thêm cuối cùng được áp dụng đầu tiên.
NSAffineTransform cho phép kết hợp nhiều biến đổi trong một đối tượng. Mỗi lần gọi translateXBy:yBy:, scaleBy: hoặc rotateByDegrees: sẽ sửa đổi ma trận bên trong — nhân ma trận hiện tại với ma trận của biến đổi tương ứng. Điều này cho phép tạo một biến đổi phức tạp từ một chuỗi các thao tác đơn giản.
Để đảo ngược một biến đổi kết hợp, sử dụng phương thức invert. Phép đảo ngược tạo ra một biến đổi affine mới hủy bỏ biến đổi gốc: nếu bạn áp dụng biến đổi gốc cho một điểm và sau đó áp dụng biến đổi đảo ngược, điểm đó sẽ trở về vị trí ban đầu. Đảo ngược hữu ích để hủy bỏ biến đổi ngữ cảnh trước khi vẽ các phần tử UI không nên bị biến đổi.
Để kiểm tra xem một biến đổi có phải là đồng nhất (không thay đổi tọa độ) hay không, sử dụng thuộc tính isIdentity. Biến đổi đồng nhất là ma trận đơn vị — nhân với nó không thay đổi tọa độ của điểm. Một đối tượng NSAffineTransform mới được tạo ra như một biến đổi đồng nhất. Thuộc tính này hữu ích cho việc tối ưu hóa: nếu biến đổi là đồng nhất, có thể bỏ qua việc áp dụng nó.
NSAffineTransform có thể được áp dụng cho bốn loại đối tượng: NSPoint, NSSize, NSBezierPath và ngữ cảnh đồ họa NSGraphicsContext. Mỗi mục tiêu có một phương thức riêng: transformPoint:, transformSize:, transformBezierPath: và set.
Phương thức transformPoint: nhân ma trận biến đổi với vector tọa độ của điểm (x, y, 1), trả về vị trí mới của điểm. transformSize: hoạt động tương tự nhưng không tính đến các thành phần tX và tY — tịnh tiến không được áp dụng, chỉ có tỷ lệ, xoay và cắt. Điều này hữu ích cho việc biến đổi các vector và kích thước không nên bị dịch chuyển.
Phương thức transformBezierPath: tạo một đối tượng NSBezierPath mới, mỗi điểm của nó được biến đổi theo ma trận NSAffineTransform. Đường dẫn gốc không bị thay đổi — một bản sao với tọa độ đã biến đổi được tạo ra. Đây là một thao tác tốn kém đối với các đường dẫn phức tạp với số lượng nút lớn.
Phương thức set áp dụng biến đổi cho ngữ cảnh đồ họa hiện tại (NSGraphicsContext), sửa đổi CTM (Ma trận biến đổi hiện tại) của nó. Sau khi gọi set, tất cả các thao tác vẽ tiếp theo trong ngữ cảnh này sẽ bị biến đổi. Đối với các biến đổi tạm thời, hãy sử dụng saveGraphicsState / restoreGraphicsState trước và sau set.
Hãy xem các ví dụ thực tế về việc sử dụng NSAffineTransform trong ứng dụng macOS để biến đổi các đối tượng đồ họa.
Ví dụ này tạo một NSAffineTransform kết hợp tịnh tiến, xoay và tỷ lệ. Đầu tiên, đối tượng được dịch chuyển (100, 50), sau đó xoay 45 độ, và cuối cùng được phóng to 1.5 lần. Thứ tự rất quan trọng: xoay sau khi tịnh tiến sẽ xoay so với gốc tọa độ mới.
import AppKit
let transform = NSAffineTransform()
transform.translateXBy(100, yBy: 50)
transform.rotateByDegrees(45)
transform.scaleBy(1.5)
// Áp dụng cho điểm
let point = NSPoint(x: 10, y: 20)
let transformedPoint = transform.transform(point)
// Áp dụng cho kích thước
let size = NSSize(width: 100, height: 50)
let transformedSize = transform.transform(size)
Ví dụ này minh họa việc áp dụng NSAffineTransform cho ngữ cảnh đồ họa thông qua set và concat. Sau khi áp dụng biến đổi, tất cả các thao tác vẽ tiếp theo được thực hiện trong hệ tọa độ đã biến đổi. Lưu trạng thái đồ họa qua saveGraphicsState cho phép quay lại ma trận gốc.
func drawWithTransform(in rect: NSRect) {
guard let ctx = NSGraphicsContext.current
else { return }
ctx.saveGraphicsState()
let xfm = NSAffineTransform()
xfm.translateXBy(rect.midX, yBy: rect.midY)
xfm.rotateByDegrees(30)
xfm.scaleBy(0.8)
xfm.concat()
// Vẽ trong hệ tọa độ đã biến đổi
let path = NSBezierPath(roundedRect: NSRect(
x: -50, y: -50,
width: 100, height: 100
), xRadius: 10, yRadius: 10)
NSColor.blue.setFill()
path.fill()
ctx.restoreGraphicsState()
}
Ví dụ này minh họa việc tạo bản sao của NSBezierPath với tọa độ đã biến đổi. Đường dẫn gốc không thay đổi và bản sao đã biến đổi có thể được vẽ riêng biệt — điều này hữu ích để tạo các phần tử lặp lại ở các vị trí khác nhau.
func createTransformedTriangle() -> NSBezierPath {
let triangle = NSBezierPath()
triangle.move(to: NSPoint(x: 0, y: 0))
triangle.line(to: NSPoint(x: 100, y: 0))
triangle.line(to: NSPoint(x: 50, y: 100))
triangle.close()
let xfm = NSAffineTransform()
xfm.translateXBy(200, yBy: 100)
xfm.scaleXBy(1.5, yBy: 1.5)
xfm.rotateByDegrees(45)
let transformed = xfm.transform(triangle)
return transformed
}
Câu hỏi thường gặp
NSAffineTransform là một lớp từ AppKit (chỉ macOS), trong khi CGAffineTransform là một cấu trúc từ Core Graphics (iOS và macOS). NSAffineTransform là một wrapper hướng đối tượng và cung cấp các phương thức để làm việc với NSBezierPath. CGAffineTransform là một cấu trúc kiểu giá trị nhẹ được sử dụng với CGPath.
Để xoay quanh tâm, thực hiện ba bước: 1) tịnh tiến đến tâm của đối tượng (di chuyển gốc tọa độ vào tâm), 2) xoay một góc mong muốn, 3) tịnh tiến ngược lại bằng vector tâm âm. Sự kết hợp tịnh tiến—xoay—tịnh tiến tạo ra phép xoay quanh một điểm bất kỳ, thay vì quanh gốc tọa độ.
Không, NSAffineTransform chỉ khả dụng trên macOS trong AppKit. Trên iOS, sử dụng cấu trúc CGAffineTransform từ Core Graphics, cung cấp các khả năng tương tự: CGAffineTransformMakeTranslation, CGAffineTransformMakeScale, CGAffineTransformMakeRotation và CGAffineTransformConcat.
Sử dụng phương thức invert, trả về biến đổi đảo ngược. Nếu bạn nhân biến đổi gốc với biến đổi đảo ngược, bạn nhận được ma trận đơn vị (biến đổi đồng nhất). Để áp dụng tạm thời, hãy gọi saveGraphicsState trước set và restoreGraphicsState sau.
Sử dụng tỷ lệ âm: transform.scaleXBy(-1, yBy: 1) tạo phản chiếu ngang, trong khi scaleXBy(1, yBy: -1) tạo phản chiếu dọc. Để phản chiếu so với một trục bất kỳ, kết hợp translate (dịch chuyển đến trục), scale với -1 và translate ngược.
Tổng kết
invert hủy bỏ mọi biến đổi affineChúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm