|拼豆制圖26:16 色圖例如何撐開 70×70 導出畫布)
標簽Harmony os、ArkTS、離屏渲染、動態(tài)布局、PNG 導出編號施工圖最容易被忽略的區(qū)域往往不是 70×70 主網(wǎng)格而是網(wǎng)格下面的色號圖例。主網(wǎng)格尺寸固定圖例卻會隨著顏色數(shù)量增加4 種顏色只占一行5 種顏色必須換到第二行16 種顏色正好鋪滿四行。若畫布仍使用固定高度最下面一行色號或頁腳會直接落到緩沖區(qū)外若一開始預留很大的空白又會讓低色數(shù)圖紙帶著一截無意義的尾部。本篇不從“讓布局看起來差不多”出發(fā)而是拆解工程里的整數(shù)幾何calculateSize()先計算 RGBA 緩沖區(qū)drawLegend()再用同一組行列常量落點。以當前 70×70、16 色圖紙為樣本可以把最終尺寸精確推導為1632×1868并解釋為什么每增加一行圖例內存會多出221,952 字節(jié)。一、先把畫布拆成六段而不是寫一個經(jīng)驗高度PatternExportService把尺寸相關常量集中在同一個服務中privatestaticreadonlycellSize:number22;privatestaticreadonlymarginLeft:number64;privatestaticreadonlymarginTop:number72;privatestaticreadonlymarginRight:number28;privatestaticreadonlyaxisBottom:number38;privatestaticreadonlylegendTop:number34;privatestaticreadonlylegendRowHeight:number34;privatestaticreadonlylegendColumns:number4;privatestaticreadonlywatermarkHeight:number48;畫布寬度由左邊距、網(wǎng)格寬度和右邊距組成高度則由頂部、網(wǎng)格、下坐標軸、圖例間隔、圖例行和頁腳組成width marginLeft pattern.width × cellSize marginRight height marginTop pattern.height × cellSize axisBottom legendTop legendRows × legendRowHeight watermarkHeight這比一個固定的canvasHeight更重要因為每一段都能在導出結果中找到對應區(qū)域。出現(xiàn)裁切時可以直接判斷是網(wǎng)格尺寸算錯、圖例行數(shù)算少還是頁腳沒有納入總高度。二、圖例行數(shù)只有一條公式但邊界值決定成敗四列布局下行數(shù)等于顏色數(shù)除以 4 后向上取整。工程中還用Math.max(1, ...)保證至少留出一行constlegendRowsMath.max(1,Math.ceil(pattern.colorStats.length/PatternExportService.legendColumns));幾個關鍵輸入如下colorStats.length計算過程圖例行數(shù)0max(1, ceil(0 / 4))14max(1, ceil(4 / 4))15max(1, ceil(5 / 4))215max(1, ceil(15 / 4))416max(1, ceil(16 / 4))4這里最危險的不是 16而是 5。只要實現(xiàn)誤用了向下取整4 色圖仍然正常5 色圖的第五項卻會占用未預留的第二行。測試樣本因此不能只選整除值至少要覆蓋4、5、16。三、70×70、16 色畫布為何恰好是 1632×1868主網(wǎng)格每格 22 像素因此網(wǎng)格寬高均為70 × 22 1540 px畫布寬度為64 1540 28 1632 px16 種顏色在四列布局中占 4 行高度為72 1540 38 34 4 × 34 48 72 1540 38 34 136 48 1868 px最終 RGBA 緩沖區(qū)包含1632 × 1868 × 4 12,194,304 bytes約為 11.63 MiB。這個數(shù)字是渲染階段的原始像素占用并不是壓縮后的 PNG 文件大小。理解兩者差異很重要圖片保存后可能只有幾百 KiB但生成過程仍要先容納完整 RGBA 數(shù)組。每新增一行圖例高度增加 34 像素對應額外內存1632 × 34 × 4 221,952 bytes約 216.75 KiB。動態(tài)高度不僅避免裁切也讓顏色較少的圖紙少申請不必要的緩沖區(qū)。四、尺寸計算和繪制必須共享同一組常量尺寸正確并不代表內容一定落在正確位置。drawLegend()從主網(wǎng)格下方開始繪制conststartX42;conststartYmarginToppattern.height*cellSizeaxisBottom18;constcolWidthMath.floor((canvas.width-84)/legendColumns);對 1632 像素寬的畫布列寬是(1632 - 84) / 4 387 px四列的起始 x 坐標依次為42、429、816、1203。70 行網(wǎng)格下第一行圖例的 y 坐標為72 70 × 22 38 18 1668 px四行 y 坐標則是1668、1702、1736、1770。每項用一個 24×24 色塊開頭色號放在x 32數(shù)量放在x 90constcoli%4;constrowMath.floor(i/4);constxstartXcol*colWidth;constystartYrow*34;canvas.fillRect(x,y,24,24,color);canvas.strokeRect(x,y,24,24,borderColor,1);drawText(canvas,stat.colorCode,x32,y4,2,textColor);drawText(canvas,${stat.count},x90,y6,1,mutedColor);calculateSize()與drawLegend()都依賴legendColumns4和legendRowHeight34。如果一處改成五列、另一處仍按四列計算畫布雖然足夠大項目卻會出現(xiàn)空白行或頁腳間距異常。因此這兩個值應該被視作跨函數(shù)契約而不是兩個方便修改的數(shù)字。五、一維統(tǒng)計數(shù)組如何穩(wěn)定映射到四列網(wǎng)格i % 4決定列floor(i / 4)決定行這種映射有兩個好處數(shù)據(jù)層繼續(xù)保持一維BeadColorStat[]無需為了界面重新組裝二維數(shù)組。第 4、5、8、9 項的換行邊界天然由整數(shù)運算確定不依賴測量文字寬度。16 項的索引分布如下第 0 行 0 1 2 3 第 1 行 4 5 6 7 第 2 行 8 9 10 11 第 3 行12 13 14 15由于工程的資源色板最多支持 16 個單字符索引四列布局剛好形成 4×4 的完整區(qū)域。若未來允許 20 或 24 色不需要改映射算法只會自然增加到 5 或 6 行。六、真正需要統(tǒng)一的是“顏色數(shù)量”的語義當前倉庫中的countColors()會遍歷整張色板并把計數(shù)為 0 的顏色也放入colorStatsfor(leti0;ipalette.length;i){// 統(tǒng)計同一 colorId 的格子數(shù)量stats.push({colorId:color.id,colorCode:color.code,colorName:color.name,hex:color.hex,count});}這意味著colorStats.length表示“色板容量”不一定表示“實際使用顏色數(shù)”。對 16 色資源而言即使某兩個色號在圖中沒有出現(xiàn)圖例仍可能保留 16 項和四行空間。這不是單純的對錯問題而是產(chǎn)品語義選擇若圖例用于備料只展示count 0的顏色更緊湊。若圖例用于說明完整色板保留 0 數(shù)量項更完整。關鍵是尺寸計算與繪制必須消費同一份數(shù)組。不能在calculateSize()前過濾一次、在drawLegend()中又使用原數(shù)組否則行數(shù)和真實內容會再次失配。更穩(wěn)妥的做法是先生成一次visibleStats之后所有圖例邏輯只讀取它。七、空圖例為何仍保留一行Math.max(1, ...)讓 0 色圖也保留 34 像素。此時COLOR LEGEND標題仍會出現(xiàn)循環(huán)內部不繪制色塊。這樣做有兩個實際好處頁腳不會突然上移到坐標軸旁邊版式仍有穩(wěn)定的視覺分區(qū)??諗?shù)據(jù)仍能導出一張結構完整的診斷圖而不是出現(xiàn)高度為 0 的特殊分支。但如果業(yè)務明確不允許空圖紙入口應在渲染前返回錯誤而不是依賴這 34 像素掩蓋數(shù)據(jù)異常。布局兜底負責內存安全業(yè)務校驗負責解釋為什么數(shù)據(jù)為空兩者職責不同。八、頁腳不被覆蓋要看最后一行的真實底邊16 色時最后一行起點是 1770色塊高度 24因此其底邊在 1794。頁腳文字從canvas.height - 40開始也就是 1828。兩者之間仍有 34 像素間隔。這個余量來自legendRowHeight、watermarkHeight與頁腳落點的共同結果。若以后把色塊從 24 放大到 32行高仍保持 34只剩 2 像素垂直間隔若再增加描邊或第二行說明文字就可能與下一行重疊。因此調整圖例視覺樣式時不能只改fillRect()還要重新核對itemVisibleHeight legendRowHeight lastItemBottom watermarkTop watermarkBottom canvas.height九、用算式和關鍵像素驗證而不是只看整圖這類離屏布局適合分三層驗證1. 幾何結果70×70、16 色1632×1868。70×70、5 色圖例 2 行高度1800。70×70、4 色圖例 1 行高度1766。2. 關鍵位置第一列起點 x 為 42。第四列起點 x 為 1203。第五項必須位于第二行即 y 為 1702。第十六項必須位于第四行第四列。3. 邊界像素最后一行色塊不能越過 RGBA 緩沖區(qū)。圖例色塊邊緣應存在 1 像素描邊。頁腳像素應位于最后一行圖例之后。整圖預覽可以發(fā)現(xiàn)明顯錯位但只有這些固定數(shù)值能快速定位 off-by-one、行數(shù)取整和高度遺漏。十、可繼續(xù)演進的布局接口當圖例未來需要支持不同列數(shù)、長色號或橫屏導出時可以把幾何計算提取為一個純結果interfaceLegendLayout{columns:number;rows:number;rowHeight:number;columnWidth:number;startX:number;startY:number;totalHeight:number;}calculateSize()與drawLegend()共同接收LegendLayout就不會各自重復公式。列數(shù)也可以根據(jù)畫布寬度選擇但仍要先得到確定的布局結果再申請像素緩沖區(qū)不能邊畫邊修改高度。小結Harmony os 的 ArkTS 離屏導出沒有自動排版系統(tǒng)畫布多高、圖例放在哪里都必須在寫入第一個像素前算清楚。當前實現(xiàn)用四列、34 像素行高和向上取整把顏色數(shù)量穩(wěn)定映射為圖例行數(shù)70×70、16 色樣本最終得到 1632×1868 的畫布每增加一行只增加 34 像素高度和約 216.75 KiB RGBA 內存。比公式本身更重要的是三條工程約束尺寸計算和繪制共享常量過濾后的統(tǒng)計數(shù)組只能生成一次并被兩處共同消費驗證必須覆蓋 4 到 5 的換行邊界。守住這三點圖例就能真正參與畫布布局而不會成為導出鏈路中最后才暴露的裁切點。