77#include < cctype>
88#include < cmath>
99#include < cstdint>
10+ #include < memory>
1011#include < string>
1112#include < utility>
1213#include < vector>
@@ -26,10 +27,18 @@ struct SubPath {
2627
2728struct RGBA { uint8_t r=0 ,g=0 ,b=0 ,a=255 ; };
2829
29- // クリップ矩形 (デバイス座標, [x0,x1) x [y0,y1))。active=false なら無制限。
30+ // クリップ領域 (デバイス座標)。
31+ // active=false … 無制限
32+ // active=true, mask=null … 矩形クリップ [x0,x1) x [y0,y1) (厳密)
33+ // mask!=null … 任意形状クリップ。x0..y1 は外接矩形 (高速リジェクト用)、
34+ // mask は surface と同寸のカバレッジ (1=内側/0=外側)。
35+ // mask は shared_ptr のため save()/restore() の state コピーで安価に共有される
36+ // (clip() は既存 mask を破壊せず新しい mask を割り当てるので復元が壊れない)。
3037struct ClipRect {
3138 bool active = false ;
3239 double x0 = 0 , y0 = 0 , x1 = 0 , y1 = 0 ;
40+ std::shared_ptr<const std::vector<uint8_t >> mask;
41+ int mask_w = 0 , mask_h = 0 ;
3342};
3443
3544// RGBA8 のピクセルバッファ。
@@ -101,6 +110,9 @@ inline void Blend(Surface& s, const ClipRect& clip, int x, int y, const RGBA& co
101110{
102111 if (x < 0 || y < 0 || x >= s.width || y >= s.height ) return ;
103112 if (clip.active && (x < clip.x0 || x >= clip.x1 || y < clip.y0 || y >= clip.y1 )) return ;
113+ // 任意形状クリップ: マスクの外なら書き込まない。
114+ if (clip.mask && (x >= clip.mask_w || y >= clip.mask_h ||
115+ (*clip.mask )[static_cast <size_t >(y) * clip.mask_w + x] == 0 )) return ;
104116 size_t i = (static_cast <size_t >(y) * s.width + x) * 4 ;
105117 double a = (col.a / 255.0 ) * alpha;
106118 s.pixels [i+0 ] = static_cast <uint8_t >(col.r * a + s.pixels [i+0 ] * (1 - a));
@@ -215,6 +227,98 @@ inline void IntersectClip(ClipRect& clip, double nx0, double ny0, double nx1, do
215227 }
216228}
217229
230+ // パスが単一の軸並行矩形か判定する (矩形なら外接矩形クリップで厳密に足りる)。
231+ // beginPath→rect() は 4 or 5 点 (閉路点重複) の矩形サブパスになる。
232+ inline bool IsAxisAlignedRect (const std::vector<SubPath>& path)
233+ {
234+ if (path.size () != 1 ) return false ;
235+ const std::vector<Point>& p = path[0 ].pts ;
236+ // 末尾が始点と重複する 5 点表現も許容する
237+ size_t n = p.size ();
238+ if (n == 5 && std::abs (p[4 ].x - p[0 ].x ) < 1e-6 && std::abs (p[4 ].y - p[0 ].y ) < 1e-6 ) {
239+ n = 4 ;
240+ }
241+ if (n != 4 ) return false ;
242+ // 各辺が水平または垂直であること
243+ for (size_t i = 0 ; i < 4 ; ++i) {
244+ const Point& a = p[i];
245+ const Point& b = p[(i + 1 ) % 4 ];
246+ const bool horizontal = std::abs (a.y - b.y ) < 1e-6 ;
247+ const bool vertical = std::abs (a.x - b.x ) < 1e-6 ;
248+ if (!horizontal && !vertical) return false ;
249+ }
250+ return true ;
251+ }
252+
253+ // パスを nonzero winding でカバレッジマスク (surface 同寸, 1=内側) にラスタライズする。
254+ // スキャンライン規約は FillPath と一致させる (塗りと同じ形にクリップされる)。
255+ inline std::shared_ptr<std::vector<uint8_t >> RasterizePathMask (
256+ const std::vector<SubPath>& path, int width, int height)
257+ {
258+ auto mask = std::make_shared<std::vector<uint8_t >>(
259+ static_cast <size_t >(width) * height, 0 );
260+ if (width <= 0 || height <= 0 ) return mask;
261+
262+ double miny = 1e18 , maxy = -1e18 ;
263+ for (auto & sp : path) for (auto & p : sp.pts ) { miny = std::min (miny,p.y ); maxy = std::max (maxy,p.y ); }
264+ if (maxy < miny) return mask;
265+ int y0 = std::max (0 , static_cast <int >(std::floor (miny)));
266+ int y1 = std::min (height - 1 , static_cast <int >(std::ceil (maxy)));
267+ for (int y = y0; y <= y1; ++y) {
268+ double sy = y + 0.5 ;
269+ std::vector<std::pair<double ,int >> xs;
270+ for (auto & sp : path) {
271+ size_t n = sp.pts .size ();
272+ if (n < 2 ) continue ;
273+ for (size_t i = 0 ; i < n; ++i) {
274+ Point a = sp.pts [i];
275+ Point b = sp.pts [(i+1 ) % n];
276+ if ((a.y <= sy && b.y > sy) || (b.y <= sy && a.y > sy)) {
277+ double t = (sy - a.y ) / (b.y - a.y );
278+ xs.push_back ({a.x + t * (b.x - a.x ), a.y < b.y ? 1 : -1 });
279+ }
280+ }
281+ }
282+ std::sort (xs.begin (), xs.end ());
283+ int wind = 0 ;
284+ for (size_t i = 0 ; i + 1 < xs.size (); ++i) {
285+ wind += xs[i].second ;
286+ if (wind != 0 ) {
287+ int xa = std::max (0 , static_cast <int >(std::floor (xs[i].first )));
288+ int xb = std::min (width - 1 , static_cast <int >(std::ceil (xs[i+1 ].first )));
289+ for (int x = xa; x <= xb; ++x) {
290+ (*mask)[static_cast <size_t >(y) * width + x] = 1 ;
291+ }
292+ }
293+ }
294+ }
295+ return mask;
296+ }
297+
298+ // 任意形状パスをクリップへ積集合で反映する (非矩形 clip() の実体)。
299+ // 外接矩形 (高速リジェクト) も同時に反映し、既存 mask があれば AND を取って
300+ // 新しい mask を割り当てる (save/restore 安全)。
301+ inline void IntersectClipMask (ClipRect& clip, const std::vector<SubPath>& path,
302+ int width, int height)
303+ {
304+ double minx, miny, maxx, maxy;
305+ if (PathBounds (path, &minx, &miny, &maxx, &maxy)) {
306+ IntersectClip (clip, std::floor (minx), std::floor (miny),
307+ std::ceil (maxx), std::ceil (maxy));
308+ }
309+ auto next = RasterizePathMask (path, width, height);
310+ if (clip.mask ) {
311+ const std::vector<uint8_t >& prev = *clip.mask ;
312+ const size_t n = std::min (next->size (), prev.size ());
313+ for (size_t i = 0 ; i < n; ++i) {
314+ if (prev[i] == 0 ) { (*next)[i] = 0 ; }
315+ }
316+ }
317+ clip.mask = next;
318+ clip.mask_w = width;
319+ clip.mask_h = height;
320+ }
321+
218322// 3次ベジェを現在サブパスへフラット化して追記する。p0 は変換済みの直前点。
219323inline void FlattenCubic (std::vector<Point>& out,
220324 Point p0, Point p1, Point p2, Point p3, int steps = 16 )
0 commit comments