大家好,我是微微笑的蝸牛器赞,??垢袱。
上一節(jié)我們講了如何確定節(jié)點布局信息,輸出了布局樹港柜。今天请契,將介紹最后一個環(huán)節(jié),繪制夏醉。內(nèi)容不多爽锥,相對也比較好理解。
附上前面幾節(jié)的鏈接畔柔,想了解的童靴可以先看看氯夷。
整體流程
整個過程如下:
- 根據(jù)布局樹生成繪制命令列表
- 光柵化,生成像素點信息
- 將像素轉(zhuǎn)換為圖片
- 顯示到屏幕
繪制命令是個啥靶擦?它描述了你該如何繪制一個圖形肠槽,比如位置大小擎淤、顏色、形狀等等秸仙。
這里嘴拢,我們只處理背景色和邊框的繪制,文字暫不涉及寂纪。所以只需支持矩形繪制席吴,知道矩形的位置大小、顏色就好捞蛋。
數(shù)據(jù)結(jié)構(gòu)
繪制命令孝冒,定義為枚舉類型。暫且只支持矩形拟杉,關(guān)聯(lián)顏色和區(qū)域庄涡。
// 繪制命令,目前只支持顏色繪制
enum DisplayCommand {
case SolidColor(Color, Rect)
}
畫布搬设,用來保存像素信息穴店。
// 畫布
struct Canvas {
var width: Int
var height: Int
// 像素點,argb
var pixels: [Color]
}
生成繪制命令
背景
背景的繪制命令比較好生成拿穴。從節(jié)點樣式表中取出背景色 background
泣洞,再計算出背景區(qū)域就好。
背景區(qū)域包括「內(nèi)容區(qū)+內(nèi)邊距+邊框」默色。
代碼如下:
// 繪制背景
func renderBackground(list: inout [DisplayCommand], layoutBox: LayoutBox) {
// 獲取背景色
if let color = getColor(layoutBox: layoutBox, name: "background") {
// 背景包括 padding + border + content
let displayCommand = DisplayCommand.SolidColor(color, layoutBox.dimensions.borderBox())
list.append(displayCommand)
}
}
邊框
同樣球凰,首先從樣式表中取出邊框顏色 border-color
。
另外腿宰,邊框分為上下左右四個矩形區(qū)域呕诉,需分別計算出來。如下所示:
繪制命令的生成跟背景色差不多吃度,只是注意下矩形區(qū)域的計算甩挫。
總繪制列表
遞歸遍歷布局樹,將每個節(jié)點的繪制命令加入到數(shù)組规肴,即可得到總繪制命令列表捶闸。
// 生成總體繪制列表
func buildDisplayList(layoutRoot: LayoutBox) -> [DisplayCommand] {
var list: [DisplayCommand] = []
renderLayoutBox(list: &list, layoutBox: layoutRoot)
return list
}
func renderLayoutBox(list: inout [DisplayCommand], layoutBox: LayoutBox) {
// 繪制背景
renderBackground(list: &list, layoutBox: layoutBox)
// 繪制邊框
renderBorder(list: &list, layoutBox: layoutBox)
// 遍歷子節(jié)點夜畴,遞歸生成命令
for child in layoutBox.children {
renderLayoutBox(list: &list, layoutBox: child)
}
}
光柵化
將繪制命令轉(zhuǎn)換為像素信息拖刃。
繪制命令中包含顏色和區(qū)域,將該區(qū)域中每個點的色值寫入像素數(shù)組就好贪绘。
// 生成像素點
mutating func genPixel(color: Color, rect: Rect) {
// 將 rect 范圍內(nèi)的點填充為 color兑牡,不能超過畫布大小
// clamp 主要是用于限制范圍
let x0 = Int(rect.x.clamp(min: 0, max: Float(self.width)))
let y0 = Int(rect.y.clamp(min: 0, max: Float(self.height)))
let x1 = Int((rect.x + rect.width).clamp(min: 0, max: Float(self.width)))
let y1 = Int((rect.y + rect.height).clamp(min: 0, max: Float(self.height)))
// 遍歷所有點,橫向一行行填充
for y in y0...y1 {
for x in x0...x1 {
let index = y * width + x
pixels[index] = color
}
}
}
生成圖片
得到像素信息數(shù)組后税灌,使用 CoreGraphics 的 api 生成圖片均函。注意 color 的順序是 argb
亿虽。
func imageFromARGB32Bitmap(pixels: [Color], width: Int, height: Int) -> UIImage? {
guard width > 0 && height > 0 else { return nil }
guard pixels.count == width * height else { return nil }
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
let bitsPerComponent = 8
let bitsPerPixel = 32
var data = pixels // Copy to mutable []
guard let providerRef = CGDataProvider(data: NSData(bytes: &data,
length: data.count * MemoryLayout<PixelData>.size)
)
else { return nil }
guard let cgim = CGImage(
width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: width * MemoryLayout<PixelData>.size,
space: rgbColorSpace,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: true,
intent: .defaultIntent
)
else { return nil }
return UIImage(cgImage: cgim)
}
顯示
這步只需創(chuàng)建一個 ImageView 將 image 顯示出來。
測試數(shù)據(jù)
測試數(shù)據(jù)在工程目錄下的 Example 下面苞也,test.html 和 test.css洛勉。
效果如下圖所示:
完整代碼可點此查看:https://github.com/silan-liu/tiny-web-render-engine-swift。
總結(jié)
這一節(jié)主要介紹了繪制的相關(guān)操作如迟,重點在于將布局信息生成繪制列表收毫,然后進行光柵化,轉(zhuǎn)換為像素點的過程殷勘。
至此此再,「聽說你想寫個渲染引擎」系列已全部完結(jié),感謝您的閱讀~