Class: Windraw::Surface

Inherits:
Object
  • Object
show all
Defined in:
lib/windraw/surface.rb,
ext/windraw/windraw.cpp

Overview

Ergonomic, hex-color-aware drawing API layered over the native Surface primitives (defined in the C++ extension). Coordinates are in pixels; the origin (0, 0) is the top-left corner.

Every drawing method returns self, so calls can be chained.

Instance Method Summary collapse

Constructor Details

#initialize(vw, vh) ⇒ Object

—- methods ————————————————————-



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'ext/windraw/windraw.cpp', line 163

static VALUE surface_initialize(VALUE self, VALUE vw, VALUE vh) {
    WindrawSurface* s = get_surface(self);
    int w = NUM2INT(vw);
    int h = NUM2INT(vh);
    if (w <= 0 || h <= 0) rb_raise(rb_eArgError, "width and height must be positive");
    s->width  = static_cast<UINT>(w);
    s->height = static_cast<UINT>(h);
    s->thread_id = GetCurrentThreadId();

    HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    if (hr == S_OK || hr == S_FALSE) {
        s->com_owned = true;          /* we incremented the init count; balance it */
    } else if (hr == RPC_E_CHANGED_MODE) {
        s->com_owned = false;         /* thread already in another apartment; usable, not ours */
    } else {
        raise_hr("CoInitializeEx", hr);
    }

    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory),
                           reinterpret_cast<void**>(&s->d2d));
    if (FAILED(hr)) raise_hr("D2D1CreateFactory", hr);

    hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
                          IID_PPV_ARGS(&s->wic));
    if (FAILED(hr)) raise_hr("CoCreateInstance(WICImagingFactory)", hr);

    hr = s->wic->CreateBitmap(s->width, s->height, GUID_WICPixelFormat32bppPBGRA,
                              WICBitmapCacheOnLoad, &s->bitmap);
    if (FAILED(hr)) raise_hr("CreateBitmap", hr);

    /* DPI 96 => 1 DIP == 1 pixel, so all coordinates are exact pixels. */
    D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
        D2D1_RENDER_TARGET_TYPE_DEFAULT,
        D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
        96.0f, 96.0f);
    hr = s->d2d->CreateWicBitmapRenderTarget(s->bitmap, props, &s->rt);
    if (FAILED(hr)) raise_hr("CreateWicBitmapRenderTarget", hr);

    s->rt->BeginDraw();
    s->draw_open = true;
    s->rt->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)); /* transparent canvas */
    return self;
}

Instance Method Details

#_clear(vr, vg, vb, va) ⇒ Object



207
208
209
210
211
212
213
214
# File 'ext/windraw/windraw.cpp', line 207

static VALUE surface_clear(VALUE self, VALUE vr, VALUE vg, VALUE vb, VALUE va) {
    WindrawSurface* s = get_surface(self);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    ensure_drawable(s);
    s->rt->Clear(D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                              static_cast<float>(b), static_cast<float>(a)));
    return self;
}

#_fill_ellipse(vcx, vcy, vrx, vry, vr, vg, vb, va) ⇒ Object



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'ext/windraw/windraw.cpp', line 260

static VALUE surface_fill_ellipse(VALUE self, VALUE vcx, VALUE vcy, VALUE vrx, VALUE vry,
                                  VALUE vr, VALUE vg, VALUE vb, VALUE va) {
    WindrawSurface* s = get_surface(self);
    double cx = NUM2DBL(vcx), cy = NUM2DBL(vcy), rx = NUM2DBL(vrx), ry = NUM2DBL(vry);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    ensure_drawable(s);

    ID2D1SolidColorBrush* brush = nullptr;
    HRESULT hr = s->rt->CreateSolidColorBrush(
        D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                     static_cast<float>(b), static_cast<float>(a)), &brush);
    if (SUCCEEDED(hr)) {
        s->rt->FillEllipse(
            D2D1::Ellipse(D2D1::Point2F(static_cast<float>(cx), static_cast<float>(cy)),
                          static_cast<float>(rx), static_cast<float>(ry)), brush);
    }
    SafeRelease(&brush);
    if (FAILED(hr)) raise_hr("CreateSolidColorBrush", hr);
    return self;
}

#_fill_polygon(arr, vr, vg, vb, va) ⇒ Object



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'ext/windraw/windraw.cpp', line 426

static VALUE surface_fill_polygon(VALUE self, VALUE arr,
                                  VALUE vr, VALUE vg, VALUE vb, VALUE va) {
    WindrawSurface* s = get_surface(self);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    ensure_drawable(s);

    ID2D1PathGeometry* geo = build_polygon_geometry(s, arr, /*closed*/ true, /*filled*/ true);
    ID2D1SolidColorBrush* brush = nullptr;
    HRESULT hr = s->rt->CreateSolidColorBrush(
        D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                     static_cast<float>(b), static_cast<float>(a)), &brush);
    if (SUCCEEDED(hr)) s->rt->FillGeometry(geo, brush);
    SafeRelease(&brush);
    SafeRelease(&geo);
    if (FAILED(hr)) raise_hr("CreateSolidColorBrush", hr);
    return self;
}

#_fill_rect(vx, vy, vw, vh, vr, vg, vb, va) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'ext/windraw/windraw.cpp', line 216

static VALUE surface_fill_rect(VALUE self, VALUE vx, VALUE vy, VALUE vw, VALUE vh,
                               VALUE vr, VALUE vg, VALUE vb, VALUE va) {
    WindrawSurface* s = get_surface(self);
    double x = NUM2DBL(vx), y = NUM2DBL(vy), w = NUM2DBL(vw), h = NUM2DBL(vh);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    ensure_drawable(s);

    ID2D1SolidColorBrush* brush = nullptr;
    HRESULT hr = s->rt->CreateSolidColorBrush(
        D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                     static_cast<float>(b), static_cast<float>(a)), &brush);
    if (SUCCEEDED(hr)) {
        s->rt->FillRectangle(
            D2D1::RectF(static_cast<float>(x), static_cast<float>(y),
                        static_cast<float>(x + w), static_cast<float>(y + h)), brush);
    }
    SafeRelease(&brush);
    if (FAILED(hr)) raise_hr("CreateSolidColorBrush", hr);
    return self;
}

#_fill_round_rect(vx, vy, vw, vh, vrx, vry, vr, vg, vb, va) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'ext/windraw/windraw.cpp', line 327

static VALUE surface_fill_round_rect(VALUE self, VALUE vx, VALUE vy, VALUE vw, VALUE vh,
                                     VALUE vrx, VALUE vry,
                                     VALUE vr, VALUE vg, VALUE vb, VALUE va) {
    WindrawSurface* s = get_surface(self);
    double x = NUM2DBL(vx), y = NUM2DBL(vy), w = NUM2DBL(vw), h = NUM2DBL(vh);
    double rx = NUM2DBL(vrx), ry = NUM2DBL(vry);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    ensure_drawable(s);

    ID2D1SolidColorBrush* brush = nullptr;
    HRESULT hr = s->rt->CreateSolidColorBrush(
        D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                     static_cast<float>(b), static_cast<float>(a)), &brush);
    if (SUCCEEDED(hr)) {
        D2D1_ROUNDED_RECT rr = D2D1::RoundedRect(
            D2D1::RectF(static_cast<float>(x), static_cast<float>(y),
                        static_cast<float>(x + w), static_cast<float>(y + h)),
            static_cast<float>(rx), static_cast<float>(ry));
        s->rt->FillRoundedRectangle(rr, brush);
    }
    SafeRelease(&brush);
    if (FAILED(hr)) raise_hr("CreateSolidColorBrush", hr);
    return self;
}

#_line(vx1, vy1, vx2, vy2, vr, vg, vb, va, vwidth) ⇒ Object



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'ext/windraw/windraw.cpp', line 304

static VALUE surface_line(VALUE self, VALUE vx1, VALUE vy1, VALUE vx2, VALUE vy2,
                          VALUE vr, VALUE vg, VALUE vb, VALUE va, VALUE vwidth) {
    WindrawSurface* s = get_surface(self);
    double x1 = NUM2DBL(vx1), y1 = NUM2DBL(vy1), x2 = NUM2DBL(vx2), y2 = NUM2DBL(vy2);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    double sw = NUM2DBL(vwidth);
    ensure_drawable(s);

    ID2D1SolidColorBrush* brush = nullptr;
    HRESULT hr = s->rt->CreateSolidColorBrush(
        D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                     static_cast<float>(b), static_cast<float>(a)), &brush);
    if (SUCCEEDED(hr)) {
        s->rt->DrawLine(
            D2D1::Point2F(static_cast<float>(x1), static_cast<float>(y1)),
            D2D1::Point2F(static_cast<float>(x2), static_cast<float>(y2)),
            brush, static_cast<float>(sw));
    }
    SafeRelease(&brush);
    if (FAILED(hr)) raise_hr("CreateSolidColorBrush", hr);
    return self;
}

#_stroke_ellipse(vcx, vcy, vrx, vry, vr, vg, vb, va, vwidth) ⇒ Object



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'ext/windraw/windraw.cpp', line 281

static VALUE surface_stroke_ellipse(VALUE self, VALUE vcx, VALUE vcy, VALUE vrx, VALUE vry,
                                    VALUE vr, VALUE vg, VALUE vb, VALUE va, VALUE vwidth) {
    WindrawSurface* s = get_surface(self);
    double cx = NUM2DBL(vcx), cy = NUM2DBL(vcy), rx = NUM2DBL(vrx), ry = NUM2DBL(vry);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    double sw = NUM2DBL(vwidth);
    ensure_drawable(s);

    ID2D1SolidColorBrush* brush = nullptr;
    HRESULT hr = s->rt->CreateSolidColorBrush(
        D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                     static_cast<float>(b), static_cast<float>(a)), &brush);
    if (SUCCEEDED(hr)) {
        s->rt->DrawEllipse(
            D2D1::Ellipse(D2D1::Point2F(static_cast<float>(cx), static_cast<float>(cy)),
                          static_cast<float>(rx), static_cast<float>(ry)),
            brush, static_cast<float>(sw));
    }
    SafeRelease(&brush);
    if (FAILED(hr)) raise_hr("CreateSolidColorBrush", hr);
    return self;
}

#_stroke_polygon(arr, vr, vg, vb, va, vwidth, vclosed) ⇒ Object



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# File 'ext/windraw/windraw.cpp', line 444

static VALUE surface_stroke_polygon(VALUE self, VALUE arr,
                                    VALUE vr, VALUE vg, VALUE vb, VALUE va,
                                    VALUE vwidth, VALUE vclosed) {
    WindrawSurface* s = get_surface(self);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    double sw = NUM2DBL(vwidth);
    bool closed = RTEST(vclosed);
    ensure_drawable(s);

    ID2D1PathGeometry* geo = build_polygon_geometry(s, arr, closed, /*filled*/ false);
    ID2D1SolidColorBrush* brush = nullptr;
    HRESULT hr = s->rt->CreateSolidColorBrush(
        D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                     static_cast<float>(b), static_cast<float>(a)), &brush);
    if (SUCCEEDED(hr)) s->rt->DrawGeometry(geo, brush, static_cast<float>(sw));
    SafeRelease(&brush);
    SafeRelease(&geo);
    if (FAILED(hr)) raise_hr("CreateSolidColorBrush", hr);
    return self;
}

#_stroke_rect(vx, vy, vw, vh, vr, vg, vb, va, vwidth) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'ext/windraw/windraw.cpp', line 237

static VALUE surface_stroke_rect(VALUE self, VALUE vx, VALUE vy, VALUE vw, VALUE vh,
                                 VALUE vr, VALUE vg, VALUE vb, VALUE va, VALUE vwidth) {
    WindrawSurface* s = get_surface(self);
    double x = NUM2DBL(vx), y = NUM2DBL(vy), w = NUM2DBL(vw), h = NUM2DBL(vh);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    double sw = NUM2DBL(vwidth);
    ensure_drawable(s);

    ID2D1SolidColorBrush* brush = nullptr;
    HRESULT hr = s->rt->CreateSolidColorBrush(
        D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                     static_cast<float>(b), static_cast<float>(a)), &brush);
    if (SUCCEEDED(hr)) {
        s->rt->DrawRectangle(
            D2D1::RectF(static_cast<float>(x), static_cast<float>(y),
                        static_cast<float>(x + w), static_cast<float>(y + h)),
            brush, static_cast<float>(sw));
    }
    SafeRelease(&brush);
    if (FAILED(hr)) raise_hr("CreateSolidColorBrush", hr);
    return self;
}

#_stroke_round_rect(vx, vy, vw, vh, vrx, vry, vr, vg, vb, va, vwidth) ⇒ Object



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'ext/windraw/windraw.cpp', line 352

static VALUE surface_stroke_round_rect(VALUE self, VALUE vx, VALUE vy, VALUE vw, VALUE vh,
                                       VALUE vrx, VALUE vry,
                                       VALUE vr, VALUE vg, VALUE vb, VALUE va, VALUE vwidth) {
    WindrawSurface* s = get_surface(self);
    double x = NUM2DBL(vx), y = NUM2DBL(vy), w = NUM2DBL(vw), h = NUM2DBL(vh);
    double rx = NUM2DBL(vrx), ry = NUM2DBL(vry);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    double sw = NUM2DBL(vwidth);
    ensure_drawable(s);

    ID2D1SolidColorBrush* brush = nullptr;
    HRESULT hr = s->rt->CreateSolidColorBrush(
        D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                     static_cast<float>(b), static_cast<float>(a)), &brush);
    if (SUCCEEDED(hr)) {
        D2D1_ROUNDED_RECT rr = D2D1::RoundedRect(
            D2D1::RectF(static_cast<float>(x), static_cast<float>(y),
                        static_cast<float>(x + w), static_cast<float>(y + h)),
            static_cast<float>(rx), static_cast<float>(ry));
        s->rt->DrawRoundedRectangle(rr, brush, static_cast<float>(sw));
    }
    SafeRelease(&brush);
    if (FAILED(hr)) raise_hr("CreateSolidColorBrush", hr);
    return self;
}

#_text(vstr, vx, vy, vr, vg, vb, va, vfont, vsize, vbold, vitalic) ⇒ Object



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
# File 'ext/windraw/windraw.cpp', line 465

static VALUE surface_text(VALUE self, VALUE vstr, VALUE vx, VALUE vy,
                          VALUE vr, VALUE vg, VALUE vb, VALUE va,
                          VALUE vfont, VALUE vsize, VALUE vbold, VALUE vitalic) {
    WindrawSurface* s = get_surface(self);
    double x = NUM2DBL(vx), y = NUM2DBL(vy), size = NUM2DBL(vsize);
    double r = NUM2DBL(vr), g = NUM2DBL(vg), b = NUM2DBL(vb), a = NUM2DBL(va);
    DWRITE_FONT_WEIGHT weight = RTEST(vbold) ? DWRITE_FONT_WEIGHT_BOLD
                                             : DWRITE_FONT_WEIGHT_NORMAL;
    DWRITE_FONT_STYLE style = RTEST(vitalic) ? DWRITE_FONT_STYLE_ITALIC
                                             : DWRITE_FONT_STYLE_NORMAL;
    /* Coerce both strings up front: if either is not String-convertible, the
     * TypeError must raise before any std::wstring exists (rb_raise longjmps
     * past C++ destructors, which would otherwise leak the first wstring). */
    StringValue(vfont);
    StringValue(vstr);
    ensure_drawable(s);

    /* Lazily create the DirectWrite factory (shared). */
    if (!s->dwrite) {
        HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
                                         reinterpret_cast<IUnknown**>(&s->dwrite));
        if (FAILED(hr)) raise_hr("DWriteCreateFactory", hr);
    }

    HRESULT hr;
    IDWriteTextFormat*    format = nullptr;
    ID2D1SolidColorBrush* brush  = nullptr;
    {
        std::wstring wfont = to_utf16(vfont);
        std::wstring wtext = to_utf16(vstr);
        hr = s->dwrite->CreateTextFormat(wfont.c_str(), nullptr, weight, style,
                 DWRITE_FONT_STRETCH_NORMAL, static_cast<float>(size), L"", &format);
        if (SUCCEEDED(hr)) {
            hr = s->rt->CreateSolidColorBrush(
                D2D1::ColorF(static_cast<float>(r), static_cast<float>(g),
                             static_cast<float>(b), static_cast<float>(a)), &brush);
        }
        if (SUCCEEDED(hr)) {
            D2D1_RECT_F rect = D2D1::RectF(static_cast<float>(x), static_cast<float>(y),
                                           static_cast<float>(s->width),
                                           static_cast<float>(s->height));
            s->rt->DrawText(wtext.c_str(), static_cast<UINT32>(wtext.length()),
                            format, rect, brush);
        }
    } /* wfont/wtext destroyed here, before any possible raise */

    SafeRelease(&brush);
    SafeRelease(&format);
    if (FAILED(hr)) raise_hr("CreateTextFormat/DrawText", hr);
    return self;
}

#circle(cx, cy, radius, fill: nil, stroke: nil, width: 1.0) ⇒ Object

Circle centered at (cx, cy) — convenience for an ellipse with equal radii.



69
70
71
# File 'lib/windraw/surface.rb', line 69

def circle(cx, cy, radius, fill: nil, stroke: nil, width: 1.0)
  ellipse(cx, cy, radius, radius, fill: fill, stroke: stroke, width: width)
end

#clear(color = "#00000000") ⇒ Object

Fill the whole surface with a single color (default fully transparent).



13
14
15
16
17
# File 'lib/windraw/surface.rb', line 13

def clear(color = "#00000000")
  r, g, b, a = Color.parse(color)
  _clear(r, g, b, a)
  self
end

#closeObject Also known as: dispose

Release all COM resources now, on the calling (creating) thread. Idempotent. After #close the surface can no longer draw or encode.



535
536
537
538
539
# File 'ext/windraw/windraw.cpp', line 535

static VALUE surface_close(VALUE self) {
    WindrawSurface* s = get_surface(self);
    if (!s->closed) surface_teardown(s);
    return self;
}

#ellipse(cx, cy, rx, ry, fill: nil, stroke: nil, width: 1.0) ⇒ Object

Ellipse centered at (cx, cy) with radii rx, ry.



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/windraw/surface.rb', line 56

def ellipse(cx, cy, rx, ry, fill: nil, stroke: nil, width: 1.0)
  if fill
    r, g, b, a = Color.parse(fill)
    _fill_ellipse(cx, cy, rx, ry, r, g, b, a)
  end
  if stroke
    r, g, b, a = Color.parse(stroke)
    _stroke_ellipse(cx, cy, rx, ry, r, g, b, a, width)
  end
  self
end

#finishObject



521
522
523
524
525
526
527
528
529
530
531
# File 'ext/windraw/windraw.cpp', line 521

static VALUE surface_finish(VALUE self) {
    WindrawSurface* s = get_surface(self);
    if (s->draw_open && s->rt) {
        HRESULT hr = s->rt->EndDraw();
        s->draw_open = false;
        s->finished  = true;
        if (FAILED(hr)) raise_hr("EndDraw", hr);
    }
    s->finished = true;
    return self;
}

#heightObject



609
# File 'ext/windraw/windraw.cpp', line 609

static VALUE surface_height(VALUE self) { return UINT2NUM(get_surface(self)->height); }

#inspectObject



115
116
117
# File 'lib/windraw/surface.rb', line 115

def inspect
  "#<Windraw::Surface #{width}x#{height}>"
end

#line(x1, y1, x2, y2, color: "#000000", width: 1.0) ⇒ Object

Straight line from (x1, y1) to (x2, y2).



74
75
76
77
78
# File 'lib/windraw/surface.rb', line 74

def line(x1, y1, x2, y2, color: "#000000", width: 1.0)
  r, g, b, a = Color.parse(color)
  _line(x1, y1, x2, y2, r, g, b, a, width)
  self
end

#polygon(points, fill: nil, stroke: nil, width: 1.0) ⇒ Object

Closed polygon through the given points (an Array of [x, y] pairs). fill: and/or stroke: hex colors; with both, fill is drawn first.



82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/windraw/surface.rb', line 82

def polygon(points, fill: nil, stroke: nil, width: 1.0)
  flat = flatten_points(points)
  if fill
    r, g, b, a = Color.parse(fill)
    _fill_polygon(flat, r, g, b, a)
  end
  if stroke
    r, g, b, a = Color.parse(stroke)
    _stroke_polygon(flat, r, g, b, a, width, true)
  end
  self
end

#polyline(points, color: "#000000", width: 1.0) ⇒ Object

Open connected line segments through the given points (Array of [x, y]).



96
97
98
99
100
# File 'lib/windraw/surface.rb', line 96

def polyline(points, color: "#000000", width: 1.0)
  r, g, b, a = Color.parse(color)
  _stroke_polygon(flatten_points(points), r, g, b, a, width, false)
  self
end

#rectangle(x, y, w, h, fill: nil, stroke: nil, width: 1.0, radius: nil) ⇒ Object Also known as: rect

Axis-aligned rectangle from (x, y) with the given width/height. Pass fill: and/or stroke: hex colors; with both, fill is drawn first. radius: rounds the corners — a single Numeric (rx == ry) or a [rx, ry] pair.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/windraw/surface.rb', line 22

def rectangle(x, y, w, h, fill: nil, stroke: nil, width: 1.0, radius: nil)
  if radius
    rx, ry =
      if radius.is_a?(Array)
        unless radius.length == 2
          raise ArgumentError, "radius: must be a Numeric or an [rx, ry] pair, got #{radius.inspect}"
        end
        radius
      else
        [radius, radius]
      end
    if fill
      r, g, b, a = Color.parse(fill)
      _fill_round_rect(x, y, w, h, rx, ry, r, g, b, a)
    end
    if stroke
      r, g, b, a = Color.parse(stroke)
      _stroke_round_rect(x, y, w, h, rx, ry, r, g, b, a, width)
    end
  else
    if fill
      r, g, b, a = Color.parse(fill)
      _fill_rect(x, y, w, h, r, g, b, a)
    end
    if stroke
      r, g, b, a = Color.parse(stroke)
      _stroke_rect(x, y, w, h, r, g, b, a, width)
    end
  end
  self
end

#save(vpath) ⇒ Object



541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
# File 'ext/windraw/windraw.cpp', line 541

static VALUE surface_save(VALUE self, VALUE vpath) {
    WindrawSurface* s = get_surface(self);
    ensure_encodable(s);
    surface_finish(self); /* flush drawing before encoding */

    IWICStream* stream = nullptr;
    HRESULT hr;
    {
        std::wstring wpath = to_utf16(vpath);
        hr = s->wic->CreateStream(&stream);
        if (SUCCEEDED(hr)) hr = stream->InitializeFromFilename(wpath.c_str(), GENERIC_WRITE);
    } /* wpath destroyed before any raise */
    if (SUCCEEDED(hr)) hr = encode_png(s, stream);
    SafeRelease(&stream);
    if (FAILED(hr)) raise_hr("save", hr);
    return vpath;
}

#sizeObject

width, height

in pixels.



111
112
113
# File 'lib/windraw/surface.rb', line 111

def size
  [width, height]
end

#text(string, x, y, color: "#000000", font: "Segoe UI", size: 16.0, bold: false, italic: false) ⇒ Object

Draw a string with its top-left at (x, y).



103
104
105
106
107
108
# File 'lib/windraw/surface.rb', line 103

def text(string, x, y, color: "#000000", font: "Segoe UI", size: 16.0,
         bold: false, italic: false)
  r, g, b, a = Color.parse(color)
  _text(string.to_s, x, y, r, g, b, a, font.to_s, size, bold, italic)
  self
end

#to_pngObject



559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
# File 'ext/windraw/windraw.cpp', line 559

static VALUE surface_to_png(VALUE self) {
    WindrawSurface* s = get_surface(self);
    ensure_encodable(s);
    surface_finish(self);

    IStream* mem = SHCreateMemStream(nullptr, 0);
    if (!mem) rb_raise(rb_eRuntimeError, "windraw: SHCreateMemStream failed");

    /* Drain the encoded PNG into a plain C buffer while the stream is alive,
     * then Release the stream BEFORE building the Ruby string. rb_str_new can
     * raise (NoMemError) via longjmp, which would otherwise skip mem->Release()
     * and leak the COM stream. We also reject >LONG_MAX sizes up front (on
     * LLP64 a cast to `long` would otherwise go negative). */
    char* buf = nullptr;
    long  buflen = 0;
    HRESULT hr = encode_png(s, mem);
    if (SUCCEEDED(hr)) {
        STATSTG st;
        hr = mem->Stat(&st, STATFLAG_NONAME);
        if (SUCCEEDED(hr)) {
            ULONGLONG full = st.cbSize.QuadPart;
            if (full > static_cast<ULONGLONG>(LONG_MAX)) {
                hr = E_OUTOFMEMORY; /* PNG too large to hand to Ruby */
            } else if (full > 0) {
                LARGE_INTEGER zero;
                zero.QuadPart = 0;
                hr = mem->Seek(zero, STREAM_SEEK_SET, nullptr);
                if (SUCCEEDED(hr)) {
                    buf = static_cast<char*>(malloc(static_cast<size_t>(full)));
                    if (!buf) {
                        hr = E_OUTOFMEMORY;
                    } else {
                        ULONG got = 0;
                        hr = mem->Read(buf, static_cast<ULONG>(full), &got);
                        if (SUCCEEDED(hr)) buflen = static_cast<long>(got);
                    }
                }
            }
        }
    }
    mem->Release();
    if (FAILED(hr)) { free(buf); raise_hr("to_png", hr); }

    VALUE result = rb_str_new(buf, buflen); /* copies; buf may be null when 0 */
    free(buf);
    rb_enc_associate(result, rb_ascii8bit_encoding());
    return result;
}

#widthObject



608
# File 'ext/windraw/windraw.cpp', line 608

static VALUE surface_width(VALUE self)  { return UINT2NUM(get_surface(self)->width); }