@@ -3,21 +3,32 @@ const Document = @import("../../models/Document.zig");
const pipeline = @import ( " pipeline.zig " ) ;
const DrawContext = pipeline . DrawContext ;
const Color = @import ( " dvui " ) . Color ;
const basic_models = @import ( " ../../models/basic_models.zig " ) ;
const Point2_f = basic_models . Point2_f ;
const Object = Document . Object ;
const default_stroke : Color . PMA = . { . r = 0 , . g = 0 , . b = 0 , . a = 255 } ;
const default_thickness : f32 = 2.0 ;
/// Эллипс с центром (0,0) и полуосями radii (обводка с учётом thickness).
pub fn draw ( ctx : * DrawContext , obj : * const Object ) void {
const std_math = std . math ;
/// Эллипс с центром (0,0) и полуосями radii. Обводка — полоса расстояния до контура (чёткая линия, не круги).
/// arc_percent: 100 — полный эллипс, иначе одна дуга; обход в коде от (0,ry) по квадрантам (визуально может казаться от низа против часовой из‑за экранной Y).
/// Отрисовка в отдельный буфер и один composite, чтобы при alpha<255 пиксели не накладывались несколько раз.
pub fn draw ( ctx : * DrawContext , obj : * const Object , allocator : std . mem . Allocator ) ! void {
const r_prop = obj . getProperty ( . radii ) orelse return ;
const rx = r_prop . radii . x ;
const ry = r_prop . radii . y ;
if ( rx < = 0 or ry < = 0 ) return ;
const stroke = if ( obj . getProperty ( . stroke_rgba ) ) | s | pipeline . rgbaToPma ( s . stroke_rgba ) else default_stroke ;
const thickness = if ( obj . getProperty ( . thickness ) ) | t | t . thickness else default_thickness ;
const arc_percent = blk : {
const p = obj . getProperty ( . arc_percent ) ;
break : blk std . math . clamp ( if ( p ) | v | v . arc_percent else 100.0 , 0.0 , 100.0 ) ;
} ;
// Полуширина обводки в нормализованных единицах (d = (x/rx)² + (y/ry)², граница при d=1).
const t = & ctx . transform ;
const min_r = @min ( rx , ry ) ;
const half_norm = thickness / ( 2.0 * min_r ) ;
const inner = @max ( 0.0 , 1.0 - half_norm ) ;
@@ -25,18 +36,25 @@ pub fn draw(ctx: *DrawContext, obj: *const Object) void {
const d_inner_sq = inner * inner ;
const d_outer_sq = outer * outer ;
const corners = [ _ ] struct { x : f32 , y : f32 } {
. { . x = - rx , . y = - ry } ,
. { . x = rx , . y = - ry } ,
. { . x = rx , . y = ry } ,
. { . x = - rx , . y = ry } ,
const margin = 1.0 + half_norm ;
const corners = [ _ ] Point2_f {
. { . x = - rx * margin , . y = - ry * margin } ,
. { . x = rx * margin , . y = - ry * margin } ,
. { . x = rx * margin , . y = ry * margin } ,
. { . x = - rx * margin , . y = ry * margin } ,
} ;
const w0 = ctx . localToWorld ( corners [ 0 ] . x , corners [ 0 ] . y ) ;
const b0 = ctx . worldToBufferF ( w0 . x , w0 . y ) ;
var min _bx : f32 = b0 . x ;
var min _by : f32 = b0 . y ;
var max_bx : f32 = b0 . x ;
var max_by : f32 = b0 . y ;
var min_bx : f32 = undefined ;
var min_by : f32 = undefined ;
var max _bx : f32 = undefined ;
var max _by : f32 = undefined ;
{
const w0 = ctx . localToWorld ( corners [ 0 ] . x , corners [ 0 ] . y ) ;
const b0 = ctx . worldToBufferF ( w0 . x , w0 . y ) ;
min_bx = b0 . x ;
min_by = b0 . y ;
max_bx = b0 . x ;
max_by = b0 . y ;
}
for ( corners [ 1 . . ] ) | c | {
const w = ctx . localToWorld ( c . x , c . y ) ;
const b = ctx . worldToBufferF ( w . x , w . y ) ;
@@ -47,50 +65,49 @@ pub fn draw(ctx: *DrawContext, obj: *const Object) void {
}
const buf_w : i32 = @intCast ( ctx . buf_width ) ;
const buf_h : i32 = @intCast ( ctx . buf_height ) ;
const x0 : i32 = @max ( 0 , @as ( i32 , @intFromFloat ( std. math . floor ( min_bx ) ) ) ) ;
const y0 : i32 = @max ( 0 , @as ( i32 , @intFromFloat ( std. math . floor ( min_by ) ) ) ) ;
const x1 : i32 = @min ( buf_w , @as ( i32 , @intFromFloat ( std. math . ceil ( max_bx ) ) ) + 1 ) ;
const y1 : i32 = @min ( buf_h , @as ( i32 , @intFromFloat ( std. math . ceil ( max_by ) ) ) + 1 ) ;
const x0 : i32 = @max ( 0 , @as ( i32 , @intFromFloat ( std_ math . floor ( min_bx ) ) ) ) ;
const y0 : i32 = @max ( 0 , @as ( i32 , @intFromFloat ( std_ math . floor ( min_by ) ) ) ) ;
const x1 : i32 = @min ( buf_w , @as ( i32 , @intFromFloat ( std_ math . ceil ( max_bx ) ) ) + 1 ) ;
const y1 : i32 = @min ( buf_h , @as ( i32 , @intFromFloat ( std_ math . ceil ( max_by ) ) ) + 1 ) ;
const buffer = try allocator . alloc ( Color . PMA , ctx . buf_width * ctx . buf_height ) ;
@memset ( buffer , . { . r = 0 , . g = 0 , . b = 0 , . a = 0 } ) ;
defer allocator . free ( buffer ) ;
var stroke_ctx = ctx . * ;
stroke_ctx . pixels = buffer ;
stroke_ctx . replace_mode = true ;
// Один раз считаем аффин buffer -> local, чтобы в цикле не вызывать cos/sin и лишние функции.
const t = & ctx . transform ;
const ctx_sx = if ( ctx . scale_x ! = 0 ) ctx . scale_x else 1.0 ;
const ctx_sy = if ( ctx . scale_y ! = 0 ) ctx . scale_y else 1.0 ;
const inv_ctx_sx = 1.0 / ctx_sx ;
const inv_ctx_sy = 1.0 / ctx_sy ;
const vx = @as ( f32 , @floatFromInt ( ctx . visible_rect . x ) ) ;
const vy = @as ( f32 , @floatFromInt ( ctx . visible_rect . y ) ) ;
const t_sx = if ( t . scale . scale_x ! = 0 ) t . scale . scale_x else 1.0 ;
const t_sy = if ( t . scale . scale_y ! = 0 ) t . scale . scale_y else 1.0 ;
const ca = std . math . cos ( - t . angle ) ;
const sa = std . math . sin ( - t . angle ) ;
const dx_off = vx * inv_ctx_sx - t . position . x ;
const dy_off = vy * inv_ctx_sy - t . position . y ;
const loc_x_off = ( dx_off * ca - dy_off * sa ) / t_sx ;
const loc_y_off = ( dx_off * sa + dy_off * ca ) / t_sy ;
const m00 = inv_ctx_sx * ca / t_sx ;
const m01 = - inv_ctx_sy * sa / t_sx ;
const m10 = inv_ctx_sx * sa / t_sy ;
const m11 = inv_ctx_sy * ca / t_sy ;
const inv_rx = 1.0 / rx ;
const inv_ry = 1.0 / ry ;
const arc_full = arc_percent > = 100.0 ;
const t_start = std_math . pi / 2.0 ;
const t_end_raw = t_start - 2.0 * std_math . pi * arc_percent / 100.0 ;
const t_end = if ( t_end_raw < = - std_math . pi ) t_end_raw + 2.0 * std_math . pi else t_end_raw ;
var by : i32 = y0 ;
while ( by < y1 ) : ( by + = 1 ) {
const buf_y = @as ( f32 , @floatFromInt ( by ) ) + 0.5 ;
const row_loc_x_off = buf_y * m01 + loc_x_off ;
const row_loc_y_off = buf_y * m11 + loc_y_off ;
var bx : i32 = x0 ;
while ( bx < x1 ) : ( bx + = 1 ) {
const buf_x = @as ( f32 , @floatFromInt ( bx ) ) + 0.5 ;
const loc_x = buf_x * m00 + row_loc_x_off ;
const loc_y = buf_x * m10 + row_loc_y_off ;
const nx = loc_ x * inv_rx ;
const ny = loc_ y * inv_ry ;
const w = stroke_ctx . bufferToWorld ( buf_x , buf_y ) ;
const loc = stroke_ctx . worldToLocal ( w . x , w . y ) ;
const nx = loc. x * inv_rx ;
const ny = loc. y * inv_ry ;
const d = nx * nx + ny * ny ;
if ( d > = d_inner_sq and d < = d_outer_sq ) {
ctx . blendPixelAtBuffer ( bx , by , stroke ) ;
if ( d < d_inner_sq or d > d_outer_sq ) continue ;
if ( ! arc_full ) {
const t_pt = std_math . atan2 ( ny , nx ) ;
const in_arc = if ( t_end < = t_start )
( t_pt > = t_end and t_pt < = t_start )
else
( t_pt > = t_end or t_pt < = t_start ) ;
if ( ! in_arc ) continue ;
}
stroke_ctx . blendPixelAtBuffer ( bx , by , stroke ) ;
}
}
ctx . compositeDrawerContext ( & stroke_ctx , t . opacity ) ;
}