iPlug2 - C++ Audio Plug-in Framework
IGraphicsSkia.cpp
1 #include <cmath>
2 #include <map>
3 
4 #include "IGraphicsSkia.h"
5 
6 #pragma warning( push )
7 #pragma warning( disable : 4244 )
8 #include "SkDashPathEffect.h"
9 #include "SkGradientShader.h"
10 #include "SkMaskFilter.h"
11 #include "SkFont.h"
12 #include "SkFontMetrics.h"
13 #include "SkTypeface.h"
14 #include "SkVertices.h"
15 #include "SkSwizzle.h"
16 #pragma warning( pop )
17 
18 #if defined OS_MAC || defined OS_IOS
19  #include "SkCGUtils.h"
20  #if defined IGRAPHICS_GL2
21  #include <OpenGL/gl.h>
22  #elif defined IGRAPHICS_GL3
23  #include <OpenGL/gl3.h>
24  #elif defined IGRAPHICS_METAL
25  //even though this is a .cpp we are in an objc(pp) compilation unit
26  #import <Metal/Metal.h>
27  #import <QuartzCore/CAMetalLayer.h>
28  #elif !defined IGRAPHICS_CPU
29  #error Define either IGRAPHICS_GL2, IGRAPHICS_GL3, IGRAPHICS_METAL, or IGRAPHICS_CPU for IGRAPHICS_SKIA with OS_MAC
30  #endif
31 #elif defined OS_WIN
32  #pragma comment(lib, "libpng.lib")
33  #pragma comment(lib, "zlib.lib")
34  #pragma comment(lib, "skia.lib")
35  #pragma comment(lib, "svg.lib")
36  #pragma comment(lib, "opengl32.lib")
37 #endif
38 
39 #if defined IGRAPHICS_GL
40  #include "gl/GrGLInterface.h"
41 #endif
42 
43 using namespace iplug;
44 using namespace igraphics;
45 
46 extern std::map<std::string, MTLTexturePtr> gTextureMap;
47 
48 #pragma mark - Private Classes and Structs
49 
51 {
52 public:
53  Bitmap(sk_sp<SkSurface> surface, int width, int height, int scale, float drawScale);
54  Bitmap(const char* path, double sourceScale);
55  Bitmap(const void* pData, int size, double sourceScale);
56  Bitmap(sk_sp<SkImage>, double sourceScale);
57 
58 private:
59  SkiaDrawable mDrawable;
60 };
61 
62 IGraphicsSkia::Bitmap::Bitmap(sk_sp<SkSurface> surface, int width, int height, int scale, float drawScale)
63 {
64  mDrawable.mSurface = surface;
65  mDrawable.mIsSurface = true;
66 
67  SetBitmap(&mDrawable, width, height, scale, drawScale);
68 }
69 
70 IGraphicsSkia::Bitmap::Bitmap(const char* path, double sourceScale)
71 {
72  sk_sp<SkData> data = SkData::MakeFromFileName(path);
73 
74  assert(data && "Unable to load file at path");
75 
76  mDrawable.mImage = SkImage::MakeFromEncoded(data);
77 
78  mDrawable.mIsSurface = false;
79  SetBitmap(&mDrawable, mDrawable.mImage->width(), mDrawable.mImage->height(), sourceScale, 1.f);
80 }
81 
82 IGraphicsSkia::Bitmap::Bitmap(const void* pData, int size, double sourceScale)
83 {
84  auto data = SkData::MakeWithoutCopy(pData, size);
85  mDrawable.mImage = SkImage::MakeFromEncoded(data);
86 
87  mDrawable.mIsSurface = false;
88  SetBitmap(&mDrawable, mDrawable.mImage->width(), mDrawable.mImage->height(), sourceScale, 1.f);
89 }
90 
91 IGraphicsSkia::Bitmap::Bitmap(sk_sp<SkImage> image, double sourceScale)
92 {
93  mDrawable.mImage = image;
94  SetBitmap(&mDrawable, mDrawable.mImage->width(), mDrawable.mImage->height(), sourceScale, 1.f);
95 }
96 
97 struct IGraphicsSkia::Font
98 {
99  Font(IFontDataPtr&& data, sk_sp<SkTypeface> typeFace)
100  : mData(std::move(data)), mTypeface(typeFace) {}
101 
102  IFontDataPtr mData;
103  sk_sp<SkTypeface> mTypeface;
104 };
105 
106 // Fonts
107 StaticStorage<IGraphicsSkia::Font> IGraphicsSkia::sFontCache;
108 
109 #pragma mark - Utility conversions
110 
111 BEGIN_IPLUG_NAMESPACE
112 BEGIN_IGRAPHICS_NAMESPACE
113 
114 SkColor SkiaColor(const IColor& color, const IBlend* pBlend)
115 {
116  if (pBlend)
117  return SkColorSetARGB(Clip(static_cast<int>(pBlend->mWeight * color.A), 0, 255), color.R, color.G, color.B);
118  else
119  return SkColorSetARGB(color.A, color.R, color.G, color.B);
120 }
121 
122 SkRect SkiaRect(const IRECT& r)
123 {
124  return SkRect::MakeLTRB(r.L, r.T, r.R, r.B);
125 }
126 
127 SkBlendMode SkiaBlendMode(const IBlend* pBlend)
128 {
129  if (!pBlend)
130  return SkBlendMode::kSrcOver;
131 
132  switch (pBlend->mMethod)
133  {
134  case EBlend::SrcOver: return SkBlendMode::kSrcOver;
135  case EBlend::SrcIn: return SkBlendMode::kSrcIn;
136  case EBlend::SrcOut: return SkBlendMode::kSrcOut;
137  case EBlend::SrcAtop: return SkBlendMode::kSrcATop;
138  case EBlend::DstOver: return SkBlendMode::kDstOver;
139  case EBlend::DstIn: return SkBlendMode::kDstIn;
140  case EBlend::DstOut: return SkBlendMode::kDstOut;
141  case EBlend::DstAtop: return SkBlendMode::kDstATop;
142  case EBlend::Add: return SkBlendMode::kPlus;
143  case EBlend::XOR: return SkBlendMode::kXor;
144  }
145 
146  return SkBlendMode::kClear;
147 }
148 
149 SkTileMode SkiaTileMode(const IPattern& pattern)
150 {
151  switch (pattern.mExtend)
152  {
153  case EPatternExtend::None: return SkTileMode::kDecal;
154  case EPatternExtend::Reflect: return SkTileMode::kMirror;
155  case EPatternExtend::Repeat: return SkTileMode::kRepeat;
156  case EPatternExtend::Pad: return SkTileMode::kClamp;
157  }
158 
159  return SkTileMode::kClamp;
160 }
161 
162 SkPaint SkiaPaint(const IPattern& pattern, const IBlend* pBlend)
163 {
164  SkPaint paint;
165  paint.setAntiAlias(true);
166  paint.setBlendMode(SkiaBlendMode(pBlend));
167  int numStops = pattern.NStops();
168 
169  if (pattern.mType == EPatternType::Solid || numStops < 2)
170  {
171  paint.setColor(SkiaColor(pattern.GetStop(0).mColor, pBlend));
172  }
173  else
174  {
175  double x1 = 0.0;
176  double y1 = 0.0;
177  double x2 = 0.0;
178  double y2 = 1.0;
179 
180  IMatrix m = pattern.mTransform;
181  m.Invert();
182  m.TransformPoint(x1, y1);
183  m.TransformPoint(x2, y2);
184 
185  SkPoint points[2] =
186  {
187  SkPoint::Make(x1, y1),
188  SkPoint::Make(x2, y2)
189  };
190 
191  SkColor colors[8];
192  SkScalar positions[8];
193 
194  assert(numStops <= 8);
195 
196  for(int i = 0; i < numStops; i++)
197  {
198  const IColorStop& stop = pattern.GetStop(i);
199  colors[i] = SkiaColor(stop.mColor, pBlend);
200  positions[i] = stop.mOffset;
201  }
202 
203  switch (pattern.mType)
204  {
205  case EPatternType::Linear:
206  paint.setShader(SkGradientShader::MakeLinear(points, colors, positions, numStops, SkiaTileMode(pattern), 0, nullptr));
207  break;
208 
209  case EPatternType::Radial:
210  {
211  float xd = points[0].x() - points[1].x();
212  float yd = points[0].y() - points[1].y();
213  float radius = std::sqrt(xd * xd + yd * yd);
214  paint.setShader(SkGradientShader::MakeRadial(points[0], radius, colors, positions, numStops, SkiaTileMode(pattern), 0, nullptr));
215  break;
216  }
217 
218  case EPatternType::Sweep:
219  {
220  SkMatrix matrix = SkMatrix::MakeAll(m.mXX, m.mYX, 0, m.mXY, m.mYY, 0, 0, 0, 1);
221 
222  paint.setShader(SkGradientShader::MakeSweep(x1, y1, colors, nullptr, numStops, SkTileMode::kDecal,
223  0, 360 * positions[numStops - 1], 0, &matrix));
224 
225  break;
226  }
227 
228  default:
229  break;
230  }
231  }
232 
233  return paint;
234 }
235 
236 END_IGRAPHICS_NAMESPACE
237 END_IPLUG_NAMESPACE
238 
239 #pragma mark -
240 
241 IGraphicsSkia::IGraphicsSkia(IGEditorDelegate& dlg, int w, int h, int fps, float scale)
242 : IGraphics(dlg, w, h, fps, scale)
243 {
244  mMainPath.setIsVolatile(true);
245 
246 #if defined IGRAPHICS_CPU
247  DBGMSG("IGraphics Skia CPU @ %i FPS\n", fps);
248 #elif defined IGRAPHICS_METAL
249  DBGMSG("IGraphics Skia METAL @ %i FPS\n", fps);
250 #elif defined IGRAPHICS_GL
251  DBGMSG("IGraphics Skia GL @ %i FPS\n", fps);
252 #endif
253  StaticStorage<Font>::Accessor storage(sFontCache);
254  storage.Retain();
255 }
256 
257 IGraphicsSkia::~IGraphicsSkia()
258 {
259  StaticStorage<Font>::Accessor storage(sFontCache);
260  storage.Release();
261 }
262 
264 {
265  char extLower[32];
266  ToLower(extLower, ext);
267  return (strstr(extLower, "png") != nullptr) || (strstr(extLower, "jpg") != nullptr) || (strstr(extLower, "jpeg") != nullptr);
268 }
269 
270 APIBitmap* IGraphicsSkia::LoadAPIBitmap(const char* fileNameOrResID, int scale, EResourceLocation location, const char* ext)
271 {
272 //#ifdef OS_IOS
273 // if (location == EResourceLocation::kPreloadedTexture)
274 // {
275 // assert(0 && "SKIA does not yet load KTX textures");
276 // GrMtlTextureInfo textureInfo;
277 // textureInfo.fTexture.retain((void*)(gTextureMap[fileNameOrResID]));
278 // id<MTLTexture> texture = (id<MTLTexture>) textureInfo.fTexture.get();
279 //
280 // MTLPixelFormat pixelFormat = texture.pixelFormat;
281 //
282 // auto grBackendTexture = GrBackendTexture(texture.width, texture.height, GrMipMapped::kNo, textureInfo);
283 //
284 // sk_sp<SkImage> image = SkImage::MakeFromTexture(mGrContext.get(), grBackendTexture, kTopLeft_GrSurfaceOrigin, kBGRA_8888_SkColorType, kOpaque_SkAlphaType, nullptr);
285 // return new Bitmap(image, scale);
286 // }
287 // else
288 //#endif
289 #ifdef OS_WIN
290  if (location == EResourceLocation::kWinBinary)
291  {
292  int size = 0;
293  const void* pData = LoadWinResource(fileNameOrResID, ext, size, GetWinModuleHandle());
294  return new Bitmap(pData, size, scale);
295  }
296  else
297 #endif
298  return new Bitmap(fileNameOrResID, scale);
299 }
300 
301 APIBitmap* IGraphicsSkia::LoadAPIBitmap(const char* name, const void* pData, int dataSize, int scale)
302 {
303  return new Bitmap(pData, dataSize, scale);
304 }
305 
307 {
308 #if defined IGRAPHICS_GL
309  auto glInterface = GrGLMakeNativeInterface();
310  mGrContext = GrDirectContext::MakeGL(glInterface);
311 #elif defined IGRAPHICS_METAL
312  CAMetalLayer* pMTLLayer = (CAMetalLayer*) pContext;
313  id<MTLDevice> device = pMTLLayer.device;
314  id<MTLCommandQueue> commandQueue = [device newCommandQueue];
315  mGrContext = GrDirectContext::MakeMetal((void*) device, (void*) commandQueue);
316  mMTLDevice = (void*) device;
317  mMTLCommandQueue = (void*) commandQueue;
318  mMTLLayer = pContext;
319 #endif
320 
321  DrawResize();
322 }
323 
325 {
326  RemoveAllControls();
327 
328 #if defined IGRAPHICS_GL
329  mSurface = nullptr;
330  mScreenSurface = nullptr;
331  mGrContext = nullptr;
332 #elif defined IGRAPHICS_METAL
333  [(id<MTLCommandQueue>) mMTLCommandQueue release];
334  mMTLCommandQueue = nullptr;
335  mMTLLayer = nullptr;
336  mMTLDevice = nullptr;
337 #endif
338 }
339 
341 {
342  auto w = static_cast<int>(std::ceil(static_cast<float>(WindowWidth()) * GetScreenScale()));
343  auto h = static_cast<int>(std::ceil(static_cast<float>(WindowHeight()) * GetScreenScale()));
344 
345 #if defined IGRAPHICS_GL || defined IGRAPHICS_METAL
346  if (mGrContext.get())
347  {
348  SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
349  mSurface = SkSurface::MakeRenderTarget(mGrContext.get(), SkBudgeted::kYes, info);
350  }
351 #else
352  #ifdef OS_WIN
353  mSurface.reset();
354 
355  const size_t bmpSize = sizeof(BITMAPINFOHEADER) + (w * h * sizeof(uint32_t));
356  mSurfaceMemory.Resize(bmpSize);
357  BITMAPINFO* bmpInfo = reinterpret_cast<BITMAPINFO*>(mSurfaceMemory.Get());
358  ZeroMemory(bmpInfo, sizeof(BITMAPINFO));
359  bmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
360  bmpInfo->bmiHeader.biWidth = w;
361  bmpInfo->bmiHeader.biHeight = -h; // negative means top-down bitmap. Skia draws top-down.
362  bmpInfo->bmiHeader.biPlanes = 1;
363  bmpInfo->bmiHeader.biBitCount = 32;
364  bmpInfo->bmiHeader.biCompression = BI_RGB;
365  void* pixels = bmpInfo->bmiColors;
366 
367  SkImageInfo info = SkImageInfo::Make(w, h, kN32_SkColorType, kPremul_SkAlphaType, nullptr);
368  mSurface = SkSurface::MakeRasterDirect(info, pixels, sizeof(uint32_t) * w);
369  #else
370  mSurface = SkSurface::MakeRasterN32Premul(w, h);
371  #endif
372 #endif
373  if (mSurface)
374  {
375  mCanvas = mSurface->getCanvas();
376  mCanvas->save();
377  }
378 }
379 
381 {
382 #if defined IGRAPHICS_GL
383  if (mGrContext.get())
384  {
385  int width = WindowWidth() * GetScreenScale();
386  int height = WindowHeight() * GetScreenScale();
387 
388  // Bind to the current main framebuffer
389  int fbo = 0, samples = 0, stencilBits = 0;
390  glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo);
391  glGetIntegerv(GL_SAMPLES, &samples);
392 #ifdef IGRAPHICS_GL3
393  glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_STENCIL, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &stencilBits);
394 #else
395  glGetIntegerv(GL_STENCIL_BITS, &stencilBits);
396 #endif
397 
398  GrGLFramebufferInfo fbinfo;
399  fbinfo.fFBOID = fbo;
400  fbinfo.fFormat = 0x8058;
401 
402  GrBackendRenderTarget backendRT(width, height, samples, stencilBits, fbinfo);
403 
404  mScreenSurface = SkSurface::MakeFromBackendRenderTarget(mGrContext.get(), backendRT, kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, nullptr, nullptr);
405  assert(mScreenSurface);
406  }
407 #elif defined IGRAPHICS_METAL
408  if (mGrContext.get())
409  {
410  int width = WindowWidth() * GetScreenScale();
411  int height = WindowHeight() * GetScreenScale();
412 
413  id<CAMetalDrawable> drawable = [(CAMetalLayer*) mMTLLayer nextDrawable];
414 
415  GrMtlTextureInfo fbInfo;
416  fbInfo.fTexture.retain((const void*)(drawable.texture));
417  GrBackendRenderTarget backendRT(width, height, 1 /* sample count/MSAA */, fbInfo);
418 
419  mScreenSurface = SkSurface::MakeFromBackendRenderTarget(mGrContext.get(), backendRT, kTopLeft_GrSurfaceOrigin, kBGRA_8888_SkColorType, nullptr, nullptr);
420 
421  mMTLDrawable = (void*) drawable;
422  assert(mScreenSurface);
423  }
424 #endif
425 
427 }
428 
429 void IGraphicsSkia::DrawImGui(SkSurface* surface)
430 {
431  #if defined IGRAPHICS_IMGUI
432  // This causes ImGui to rebuild vertex/index data based on all immediate-mode commands
433  // (widgets, etc...) that have been issued
434  ImGui::Render();
435 
436  // Then we fetch the most recent data, and convert it so we can render with Skia
437  const ImDrawData* drawData = ImGui::GetDrawData();
438  SkTDArray<SkPoint> pos;
439  SkTDArray<SkPoint> uv;
440  SkTDArray<SkColor> color;
441 
442  auto canvas = surface->getCanvas();
443 
444  for (int i = 0; i < drawData->CmdListsCount; ++i) {
445  const ImDrawList* drawList = drawData->CmdLists[i];
446 
447  // De-interleave all vertex data (sigh), convert to Skia types
448  pos.rewind(); uv.rewind(); color.rewind();
449  for (int j = 0; j < drawList->VtxBuffer.size(); ++j) {
450  const ImDrawVert& vert = drawList->VtxBuffer[j];
451  pos.push_back(SkPoint::Make(vert.pos.x * GetScreenScale(), vert.pos.y * GetScreenScale()));
452  uv.push_back(SkPoint::Make(vert.uv.x, vert.uv.y));
453  color.push_back(vert.col);
454  }
455  // ImGui colors are RGBA
456  SkSwapRB(color.begin(), color.begin(), color.count());
457 
458  int indexOffset = 0;
459 
460  // Draw everything with canvas.drawVertices...
461  for (int j = 0; j < drawList->CmdBuffer.size(); ++j)
462  {
463  const ImDrawCmd* drawCmd = &drawList->CmdBuffer[j];
464 
465  SkAutoCanvasRestore acr(canvas, true);
466 
467  // TODO: Find min/max index for each draw, so we know how many vertices (sigh)
468  if (drawCmd->UserCallback)
469  {
470  drawCmd->UserCallback(drawList, drawCmd);
471  }
472  else
473  {
474  SkPaint* paint = static_cast<SkPaint*>(drawCmd->TextureId);
475  SkASSERT(paint);
476 
477  canvas->clipRect(SkRect::MakeLTRB(drawCmd->ClipRect.x * GetScreenScale(),
478  drawCmd->ClipRect.y * GetScreenScale(),
479  drawCmd->ClipRect.z * GetScreenScale(),
480  drawCmd->ClipRect.w * GetScreenScale()));
481 
482  auto vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
483  drawList->VtxBuffer.size(),
484  pos.begin(), uv.begin(), color.begin(),
485  drawCmd->ElemCount,
486  drawList->IdxBuffer.begin() + indexOffset);
487 
488  canvas->drawVertices(vertices, SkBlendMode::kModulate, mImGuiRenderer->fFontPaint);
489 
490  indexOffset += drawCmd->ElemCount;
491  }
492  }
493  }
494  #endif
495 }
496 
498 {
499 #ifdef IGRAPHICS_CPU
500  #if defined OS_MAC || defined OS_IOS
501  SkPixmap pixmap;
502  mSurface->peekPixels(&pixmap);
503  SkBitmap bmp;
504  bmp.installPixels(pixmap);
505  CGContext* pCGContext = (CGContextRef) GetPlatformContext();
506  CGContextSaveGState(pCGContext);
507  CGContextScaleCTM(pCGContext, 1.0 / GetScreenScale(), 1.0 / GetScreenScale());
508  SkCGDrawBitmap(pCGContext, bmp, 0, 0);
509  CGContextRestoreGState(pCGContext);
510  #elif defined OS_WIN
511  auto w = WindowWidth() * GetScreenScale();
512  auto h = WindowHeight() * GetScreenScale();
513  BITMAPINFO* bmpInfo = reinterpret_cast<BITMAPINFO*>(mSurfaceMemory.Get());
514  HWND hWnd = (HWND) GetWindow();
515  PAINTSTRUCT ps;
516  HDC hdc = BeginPaint(hWnd, &ps);
517  StretchDIBits(hdc, 0, 0, w, h, 0, 0, w, h, bmpInfo->bmiColors, bmpInfo, DIB_RGB_COLORS, SRCCOPY);
518  ReleaseDC(hWnd, hdc);
519  EndPaint(hWnd, &ps);
520  #else
521  #error NOT IMPLEMENTED
522  #endif
523 #else // GPU
524  mSurface->draw(mScreenSurface->getCanvas(), 0.0, 0.0, nullptr);
525 
526  #if defined IGRAPHICS_IMGUI && !IGRAPHICS_CPU
527  if(mImGuiRenderer)
528  {
529  mImGuiRenderer->NewFrame();
530  DrawImGui(mScreenSurface.get());
531  }
532  #endif
533 
534  mScreenSurface->getCanvas()->flush();
535 
536  #ifdef IGRAPHICS_METAL
537  id<MTLCommandBuffer> commandBuffer = [(id<MTLCommandQueue>) mMTLCommandQueue commandBuffer];
538  commandBuffer.label = @"Present";
539 
540  [commandBuffer presentDrawable:(id<CAMetalDrawable>) mMTLDrawable];
541  [commandBuffer commit];
542  #endif
543 #endif
544 }
545 
546 void IGraphicsSkia::DrawBitmap(const IBitmap& bitmap, const IRECT& dest, int srcX, int srcY, const IBlend* pBlend)
547 {
548  SkPaint p;
549 
550  p.setFilterQuality(kHigh_SkFilterQuality);
551  p.setAntiAlias(true);
552  p.setBlendMode(SkiaBlendMode(pBlend));
553  if (pBlend)
554  p.setAlpha(Clip(static_cast<int>(pBlend->mWeight * 255), 0, 255));
555 
556  SkiaDrawable* image = bitmap.GetAPIBitmap()->GetBitmap();
557 
558  double scale1 = 1.0 / (bitmap.GetScale() * bitmap.GetDrawScale());
559  double scale2 = bitmap.GetScale() * bitmap.GetDrawScale();
560 
561  mCanvas->save();
562  mCanvas->clipRect(SkiaRect(dest));
563  mCanvas->translate(dest.L, dest.T);
564  mCanvas->scale(scale1, scale1);
565  mCanvas->translate(-srcX * scale2, -srcY * scale2);
566 
567  if (image->mIsSurface)
568  image->mSurface->draw(mCanvas, 0.0, 0.0, &p);
569  else
570  mCanvas->drawImage(image->mImage, 0.0, 0.0, &p);
571 
572  mCanvas->restore();
573 }
574 
575 void IGraphicsSkia::PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding)
576 {
577  SkPath arc;
578  arc.setIsVolatile(true);
579 
580  float sweep = (a2 - a1);
581 
582  if (sweep >= 360.f || sweep <= -360.f)
583  {
584  arc.addCircle(cx, cy, r);
585  mMainPath.addPath(arc, mMatrix, SkPath::kAppend_AddPathMode);
586  }
587  else
588  {
589  if (winding == EWinding::CW)
590  {
591  while (sweep < 0)
592  sweep += 360.f;
593  }
594  else
595  {
596  while (sweep > 0)
597  sweep -= 360.f;
598  }
599 
600  arc.arcTo(SkRect::MakeLTRB(cx - r, cy - r, cx + r, cy + r), a1 - 90.f, sweep, false);
601  mMainPath.addPath(arc, mMatrix, SkPath::kExtend_AddPathMode);
602  }
603 }
604 
606 {
607  SkBitmap bitmap;
608  bitmap.allocPixels(SkImageInfo::MakeN32Premul(1, 1));
609  mCanvas->readPixels(bitmap, x, y);
610  auto color = bitmap.getColor(0,0);
611  return IColor(SkColorGetA(color), SkColorGetR(color), SkColorGetG(color), SkColorGetB(color));
612 }
613 
614 bool IGraphicsSkia::LoadAPIFont(const char* fontID, const PlatformFontPtr& font)
615 {
616  StaticStorage<Font>::Accessor storage(sFontCache);
617  Font* cached = storage.Find(fontID);
618 
619  if (cached)
620  return true;
621 
622  IFontDataPtr data = font->GetFontData();
623 
624  if (data->IsValid())
625  {
626  auto wrappedData = SkData::MakeWithoutCopy(data->Get(), data->GetSize());
627  int index = data->GetFaceIdx();
628  auto typeface = SkTypeface::MakeFromData(wrappedData, index);
629 
630  if (typeface)
631  {
632  storage.Add(new Font(std::move(data), typeface), fontID);
633  return true;
634  }
635  }
636 
637  return false;
638 }
639 
640 void IGraphicsSkia::PrepareAndMeasureText(const IText& text, const char* str, IRECT& r, double& x, double & y, SkFont& font) const
641 {
642  SkFontMetrics metrics;
643  SkPaint paint;
644  //SkRect bounds;
645 
646  StaticStorage<Font>::Accessor storage(sFontCache);
647  Font* pFont = storage.Find(text.mFont);
648 
649  assert(pFont && "No font found - did you forget to load it?");
650 
651  font.setTypeface(pFont->mTypeface);
652  font.setHinting(SkFontHinting::kSlight);
653  font.setForceAutoHinting(false);
654  font.setSubpixel(true);
655  font.setSize(text.mSize * pFont->mData->GetHeightEMRatio());
656 
657  // Draw / measure
658  const double textWidth = font.measureText(str, strlen(str), SkTextEncoding::kUTF8, nullptr/* &bounds*/);
659  font.getMetrics(&metrics);
660 
661  const double textHeight = text.mSize;
662  const double ascender = metrics.fAscent;
663  const double descender = metrics.fDescent;
664 
665  switch (text.mAlign)
666  {
667  case EAlign::Near: x = r.L; break;
668  case EAlign::Center: x = r.MW() - (textWidth / 2.0); break;
669  case EAlign::Far: x = r.R - textWidth; break;
670  }
671 
672  switch (text.mVAlign)
673  {
674  case EVAlign::Top: y = r.T - ascender; break;
675  case EVAlign::Middle: y = r.MH() - descender + (textHeight / 2.0); break;
676  case EVAlign::Bottom: y = r.B - descender; break;
677  }
678 
679  r = IRECT((float) x, (float) y + ascender, (float) (x + textWidth), (float) (y + ascender + textHeight));
680 }
681 
682 float IGraphicsSkia::DoMeasureText(const IText& text, const char* str, IRECT& bounds) const
683 {
684  SkFont font;
685  font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
686 
687  IRECT r = bounds;
688  double x, y;
689  PrepareAndMeasureText(text, str, bounds, x, y, font);
690  DoMeasureTextRotation(text, r, bounds);
691  return bounds.W();
692 }
693 
694 void IGraphicsSkia::DoDrawText(const IText& text, const char* str, const IRECT& bounds, const IBlend* pBlend)
695 {
696  IRECT measured = bounds;
697 
698  SkFont font;
699  font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
700 
701  double x, y;
702 
703  PrepareAndMeasureText(text, str, measured, x, y, font);
704  PathTransformSave();
705  DoTextRotation(text, bounds, measured);
706  SkPaint paint;
707  paint.setColor(SkiaColor(text.mFGColor, pBlend));
708  mCanvas->drawSimpleText(str, strlen(str), SkTextEncoding::kUTF8, x, y, font, paint);
709  PathTransformRestore();
710 }
711 
712 void IGraphicsSkia::PathStroke(const IPattern& pattern, float thickness, const IStrokeOptions& options, const IBlend* pBlend)
713 {
714  SkPaint paint = SkiaPaint(pattern, pBlend);
715  paint.setStyle(SkPaint::kStroke_Style);
716 
717  switch (options.mCapOption)
718  {
719  case ELineCap::Butt: paint.setStrokeCap(SkPaint::kButt_Cap); break;
720  case ELineCap::Round: paint.setStrokeCap(SkPaint::kRound_Cap); break;
721  case ELineCap::Square: paint.setStrokeCap(SkPaint::kSquare_Cap); break;
722  }
723 
724  switch (options.mJoinOption)
725  {
726  case ELineJoin::Miter: paint.setStrokeJoin(SkPaint::kMiter_Join); break;
727  case ELineJoin::Round: paint.setStrokeJoin(SkPaint::kRound_Join); break;
728  case ELineJoin::Bevel: paint.setStrokeJoin(SkPaint::kBevel_Join); break;
729  }
730 
731  if (options.mDash.GetCount())
732  {
733  // N.B. support odd counts by reading the array twice
734  int dashCount = options.mDash.GetCount();
735  int dashMax = dashCount & 1 ? dashCount * 2 : dashCount;
736  float dashArray[16];
737 
738  for (int i = 0; i < dashMax; i += 2)
739  {
740  dashArray[i + 0] = options.mDash.GetArray()[i % dashCount];
741  dashArray[i + 1] = options.mDash.GetArray()[(i + 1) % dashCount];
742  }
743 
744  paint.setPathEffect(SkDashPathEffect::Make(dashArray, dashMax, options.mDash.GetOffset()));
745  }
746 
747  paint.setStrokeWidth(thickness);
748  paint.setStrokeMiter(options.mMiterLimit);
749 
750  RenderPath(paint);
751 
752  if (!options.mPreserve)
753  mMainPath.reset();
754 }
755 
756 void IGraphicsSkia::PathFill(const IPattern& pattern, const IFillOptions& options, const IBlend* pBlend)
757 {
758  SkPaint paint = SkiaPaint(pattern, pBlend);
759  paint.setStyle(SkPaint::kFill_Style);
760 
761  if (options.mFillRule == EFillRule::Winding)
762  mMainPath.setFillType(SkPathFillType::kWinding);
763  else
764  mMainPath.setFillType(SkPathFillType::kEvenOdd);
765 
766  RenderPath(paint);
767 
768  if (!options.mPreserve)
769  mMainPath.reset();
770 }
771 
772 #ifdef IGRAPHICS_DRAWFILL_DIRECT
773 void IGraphicsSkia::DrawRect(const IColor& color, const IRECT& bounds, const IBlend* pBlend, float thickness)
774 {
775  auto paint = SkiaPaint(color, pBlend);
776  paint.setStyle(SkPaint::Style::kStroke_Style);
777  paint.setStrokeWidth(thickness);
778  mCanvas->drawRect(SkiaRect(bounds), paint);
779 }
780 
781 void IGraphicsSkia::DrawRoundRect(const IColor& color, const IRECT& bounds, float cornerRadius, const IBlend* pBlend, float thickness)
782 {
783  auto paint = SkiaPaint(color, pBlend);
784  paint.setStyle(SkPaint::Style::kStroke_Style);
785  paint.setStrokeWidth(thickness);
786  mCanvas->drawRoundRect(SkiaRect(bounds), cornerRadius, cornerRadius, paint);
787 }
788 
789 void IGraphicsSkia::DrawArc(const IColor& color, float cx, float cy, float r, float a1, float a2, const IBlend* pBlend, float thickness)
790 {
791  auto paint = SkiaPaint(color, pBlend);
792  paint.setStyle(SkPaint::Style::kStroke_Style);
793  paint.setStrokeWidth(thickness);
794  mCanvas->drawArc(SkRect::MakeLTRB(cx - r, cy - r, cx + r, cy + r), a1 - 90.f, (a2 - a1), false, paint);
795 }
796 
797 void IGraphicsSkia::DrawCircle(const IColor& color, float cx, float cy, float r, const IBlend* pBlend, float thickness)
798 {
799  auto paint = SkiaPaint(color, pBlend);
800  paint.setStyle(SkPaint::Style::kStroke_Style);
801  paint.setStrokeWidth(thickness);
802  mCanvas->drawCircle(cx, cy, r, paint);
803 }
804 
805 void IGraphicsSkia::DrawEllipse(const IColor& color, const IRECT& bounds, const IBlend* pBlend, float thickness)
806 {
807  auto paint = SkiaPaint(color, pBlend);
808  paint.setStyle(SkPaint::Style::kStroke_Style);
809  paint.setStrokeWidth(thickness);
810  mCanvas->drawOval(SkiaRect(bounds), paint);
811 }
812 
813 void IGraphicsSkia::FillRect(const IColor& color, const IRECT& bounds, const IBlend* pBlend)
814 {
815  auto paint = SkiaPaint(color, pBlend);
816  paint.setStyle(SkPaint::Style::kFill_Style);
817  mCanvas->drawRect(SkiaRect(bounds), paint);
818 }
819 
820 void IGraphicsSkia::FillRoundRect(const IColor& color, const IRECT& bounds, float cornerRadius, const IBlend* pBlend)
821 {
822  auto paint = SkiaPaint(color, pBlend);
823  paint.setStyle(SkPaint::Style::kFill_Style);
824  mCanvas->drawRoundRect(SkiaRect(bounds), cornerRadius, cornerRadius, paint);
825 }
826 
827 void IGraphicsSkia::FillArc(const IColor& color, float cx, float cy, float r, float a1, float a2, const IBlend* pBlend)
828 {
829  auto paint = SkiaPaint(color, pBlend);
830  paint.setStyle(SkPaint::Style::kFill_Style);
831  mCanvas->drawArc(SkRect::MakeLTRB(cx - r, cy - r, cx + r, cy + r), a1 - 90.f, (a2 - a1), true, paint);
832 }
833 
834 void IGraphicsSkia::FillCircle(const IColor& color, float cx, float cy, float r, const IBlend* pBlend)
835 {
836  auto paint = SkiaPaint(color, pBlend);
837  paint.setStyle(SkPaint::Style::kFill_Style);
838  mCanvas->drawCircle(cx, cy, r, paint);
839 }
840 
841 void IGraphicsSkia::FillEllipse(const IColor& color, const IRECT& bounds, const IBlend* pBlend)
842 {
843  auto paint = SkiaPaint(color, pBlend);
844  paint.setStyle(SkPaint::Style::kFill_Style);
845  mCanvas->drawOval(SkiaRect(bounds), paint);
846 }
847 #endif
848 
849 void IGraphicsSkia::RenderPath(SkPaint& paint)
850 {
851  SkMatrix invMatrix;
852 
853  if (!mMatrix.isIdentity() && mMatrix.invert(&invMatrix))
854  {
855  SkPath path;
856  path.setIsVolatile(true);
857  mMainPath.transform(invMatrix, &path);
858  mCanvas->drawPath(path, paint);
859  }
860  else
861  {
862  mCanvas->drawPath(mMainPath, paint);
863  }
864 }
865 
866 void IGraphicsSkia::PathTransformSetMatrix(const IMatrix& m)
867 {
868  double xTranslate = 0.0;
869  double yTranslate = 0.0;
870 
871  if (!mCanvas)
872  return;
873 
874  if (!mLayers.empty())
875  {
876  IRECT bounds = mLayers.top()->Bounds();
877 
878  xTranslate = -bounds.L;
879  yTranslate = -bounds.T;
880  }
881 
882  mMatrix = SkMatrix::MakeAll(m.mXX, m.mXY, m.mTX, m.mYX, m.mYY, m.mTY, 0, 0, 1);
883  auto scale = GetTotalScale();
884  SkMatrix globalMatrix = SkMatrix::Scale(scale, scale);
885  mClipMatrix = SkMatrix();
886  mFinalMatrix = mMatrix;
887  globalMatrix.preTranslate(xTranslate, yTranslate);
888  mClipMatrix.postConcat(globalMatrix);
889  mFinalMatrix.postConcat(globalMatrix);
890  mCanvas->setMatrix(mFinalMatrix);
891 }
892 
893 void IGraphicsSkia::SetClipRegion(const IRECT& r)
894 {
895  mCanvas->restoreToCount(0);
896  mCanvas->save();
897  mCanvas->setMatrix(mClipMatrix);
898  mCanvas->clipRect(SkiaRect(r));
899  mCanvas->setMatrix(mFinalMatrix);
900 }
901 
902 APIBitmap* IGraphicsSkia::CreateAPIBitmap(int width, int height, int scale, double drawScale, bool cacheable)
903 {
904  sk_sp<SkSurface> surface;
905 
906  #ifndef IGRAPHICS_CPU
907  SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
908  if (cacheable)
909  {
910  surface = SkSurface::MakeRasterN32Premul(width, height);
911  }
912  else
913  {
914  surface = SkSurface::MakeRenderTarget(mGrContext.get(), SkBudgeted::kYes, info);
915  }
916  #else
917  surface = SkSurface::MakeRasterN32Premul(width, height);
918  #endif
919 
920  surface->getCanvas()->save();
921 
922  return new Bitmap(std::move(surface), width, height, scale, drawScale);
923 }
924 
926 {
927  mCanvas = mLayers.empty() ? mSurface->getCanvas() : mLayers.top()->GetAPIBitmap()->GetBitmap()->mSurface->getCanvas();
928 }
929 
930 static size_t CalcRowBytes(int width)
931 {
932  width = ((width + 7) & (-8));
933  return width * sizeof(uint32_t);
934 }
935 
936 void IGraphicsSkia::GetLayerBitmapData(const ILayerPtr& layer, RawBitmapData& data)
937 {
938  SkiaDrawable* pDrawable = layer->GetAPIBitmap()->GetBitmap();
939  size_t rowBytes = CalcRowBytes(pDrawable->mSurface->width());
940  int size = pDrawable->mSurface->height() * static_cast<int>(rowBytes);
941 
942  data.Resize(size);
943 
944  if (data.GetSize() >= size)
945  {
946  SkImageInfo info = SkImageInfo::MakeN32Premul(pDrawable->mSurface->width(), pDrawable->mSurface->height());
947  pDrawable->mSurface->readPixels(info, data.Get(), rowBytes, 0, 0);
948  }
949 }
950 
951 void IGraphicsSkia::ApplyShadowMask(ILayerPtr& layer, RawBitmapData& mask, const IShadow& shadow)
952 {
953  SkiaDrawable* pDrawable = layer->GetAPIBitmap()->GetBitmap();
954  int width = pDrawable->mSurface->width();
955  int height = pDrawable->mSurface->height();
956  size_t rowBytes = CalcRowBytes(width);
957  double scale = layer->GetAPIBitmap()->GetDrawScale() * layer->GetAPIBitmap()->GetScale();
958 
959  SkCanvas* pCanvas = pDrawable->mSurface->getCanvas();
960 
961  SkMatrix m;
962  m.reset();
963 
964  SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
965  SkPixmap pixMap(info, mask.Get(), rowBytes);
966  sk_sp<SkImage> image = SkImage::MakeFromRaster(pixMap, nullptr, nullptr);
967  sk_sp<SkImage> foreground;
968 
969  // Copy the foreground if needed
970 
971  if (shadow.mDrawForeground)
972  foreground = pDrawable->mSurface->makeImageSnapshot();
973 
974  pCanvas->clear(SK_ColorTRANSPARENT);
975 
976  IBlend blend(EBlend::Default, shadow.mOpacity);
977  pCanvas->setMatrix(m);
978  pCanvas->drawImage(image.get(), shadow.mXOffset * scale, shadow.mYOffset * scale);
979  m = SkMatrix::Scale(scale, scale);
980  pCanvas->setMatrix(m);
981  pCanvas->translate(-layer->Bounds().L, -layer->Bounds().T);
982  SkPaint p = SkiaPaint(shadow.mPattern, &blend);
983  p.setBlendMode(SkBlendMode::kSrcIn);
984  pCanvas->drawPaint(p);
985 
986  if (shadow.mDrawForeground)
987  {
988  m.reset();
989  pCanvas->setMatrix(m);
990  pCanvas->drawImage(foreground.get(), 0.0, 0.0);
991  }
992 }
993 
994 void IGraphicsSkia::DrawFastDropShadow(const IRECT& innerBounds, const IRECT& outerBounds, float xyDrop, float roundness, float blur, IBlend* pBlend)
995 {
996  SkRect r = SkiaRect(innerBounds.GetTranslated(xyDrop, xyDrop));
997 
998  SkPaint paint = SkiaPaint(COLOR_BLACK_DROP_SHADOW, pBlend);
999  paint.setStyle(SkPaint::Style::kFill_Style);
1000 
1001  paint.setMaskFilter(SkMaskFilter::MakeBlur(kSolid_SkBlurStyle, blur * 0.5)); // 0.5 seems to match nanovg
1002  mCanvas->drawRoundRect(r, roundness, roundness, paint);
1003 }
1004 
1006 {
1007 #ifdef IGRAPHICS_CPU
1008  return "SKIA | CPU";
1009 #elif defined IGRAPHICS_GL2
1010  return "SKIA | GL2";
1011 #elif defined IGRAPHICS_GL3
1012  return "SKIA | GL3";
1013 #elif defined IGRAPHICS_METAL
1014  return "SKIA | Metal";
1015 #endif
1016 }
const char * GetDrawingAPIStr() override
void UpdateLayer() override
Implemented by a graphics backend to prepare for drawing to the layer at the top of the stack...
float MW() const
float MH() const
Used to manage a rectangular area, independent of draw class/platform.
virtual void DrawRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0, float thickness=1.f)
Draw a rectangle to the graphics context.
Definition: IGraphics.cpp:2475
Used to manage composite/blend operations, independent of draw class/platform.
User-facing bitmap abstraction that you use to manage bitmap data, independant of draw class/platform...
Used to manage fill behaviour for path based drawing back ends.
bool BitmapExtSupported(const char *ext) override
Checks a file extension and reports whether this drawing API supports loading that extension...
IMatrix & Invert()
Changes the matrix to be the inverse of its original value.
virtual void BeginFrame()
Called at the beginning of drawing.
Definition: IGraphics.cpp:823
const void * LoadWinResource(const char *resID, const char *type, int &sizeInBytes, void *pHInstance)
Load a resource from the binary (windows only).
APIBitmap * CreateAPIBitmap(int width, int height, int scale, double drawScale, bool cacheable=false) override
Creates a new API bitmap, either in memory or as a GPU texture.
virtual void FillCircle(const IColor &color, float cx, float cy, float r, const IBlend *pBlend=0)
Fill a circle with a color.
Definition: IGraphics.cpp:2584
void GetLayerBitmapData(const ILayerPtr &layer, RawBitmapData &data) override
Get the contents of a layers pixels as bitmap data.
void PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding) override
Add an arc to the current path.
virtual void DrawRoundRect(const IColor &color, const IRECT &bounds, float cornerRadius=5.f, const IBlend *pBlend=0, float thickness=1.f)
Draw a rounded rectangle to the graphics context.
Definition: IGraphics.cpp:2482
Used to manage color data, independent of draw class/platform.
IRECT GetTranslated(float x, float y) const
Get a translated copy of this rectangle.
void DrawBitmap(const IBitmap &bitmap, const IRECT &dest, int srcX, int srcY, const IBlend *pBlend) override
Draw a bitmap (raster) image to the graphics context.
Used to manage stroke behaviour for path based drawing back ends.
void EndFrame() override
Called by some drawing API classes to finally blit the draw bitmap onto the screen or perform other c...
int GetScale() const
float R
Right side of the rectangle (X + W)
BitmapData GetBitmap() const
void DrawResize() override
An editor delegate base class for a SOMETHING that uses IGraphics for it&#39;s UI.
float W() const
int NStops() const
IText is used to manage font and text/text entry style for a piece of text on the UI...
virtual void DrawCircle(const IColor &color, float cx, float cy, float r, const IBlend *pBlend=0, float thickness=1.f)
Draw a circle to the graphics context.
Definition: IGraphics.cpp:2510
float DoMeasureText(const IText &text, const char *str, IRECT &bounds) const override
const IColorStop & GetStop(int idx) const
Get the IColorStop at a particular index (will crash if out of bounds)
virtual void FillArc(const IColor &color, float cx, float cy, float r, float a1, float a2, const IBlend *pBlend=0)
Fill an arc segment with a color.
Definition: IGraphics.cpp:2575
void OnViewDestroyed() override
Called after a platform view is destroyed, so that drawing classes can e.g.
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
Used to specify properties of a drop-shadow to a layer.
void ApplyShadowMask(ILayerPtr &layer, RawBitmapData &mask, const IShadow &shadow) override
Implemented by a graphics backend to apply a calculated shadow mask to a layer, according to the shad...
void OnViewInitialized(void *pContext) override
Called after platform view initialization, so that drawing classes can e.g.
virtual void FillRoundRect(const IColor &color, const IRECT &bounds, float cornerRadius=5.f, const IBlend *pBlend=0)
Fill a rounded rectangle with a color.
Definition: IGraphics.cpp:2554
Used to represent a point/stop in a gradient.
virtual void FillRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0)
Fill a rectangular region of the graphics context with a color.
Definition: IGraphics.cpp:2547
BEGIN_IPLUG_NAMESPACE T Clip(T x, T lo, T hi)
Clips the value x between lo and hi.
virtual void FillEllipse(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0)
Fill an ellipse within a rectangular region of the graphics context.
Definition: IGraphics.cpp:2591
virtual void DrawEllipse(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0, float thickness=1.f)
Draw an ellipse within a rectangular region of the graphics context.
Definition: IGraphics.cpp:2526
void TransformPoint(double &x, double &y, double x0, double y0) const
Transforms the point x, y.
IColor GetPoint(int x, int y) override
Get the color at an X, Y location in the graphics context.
void DoDrawText(const IText &text, const char *str, const IRECT &bounds, const IBlend *pBlend) override
A base class interface for a bitmap abstraction around the different drawing back end bitmap represen...
void PathStroke(const IPattern &pattern, float thickness, const IStrokeOptions &options, const IBlend *pBlend) override
Stroke the current current path.
float GetDrawScale() const
float L
Left side of the rectangle (X)
bool LoadAPIFont(const char *fontID, const PlatformFontPtr &font) override
Drawing API method to load a font from a PlatformFontPtr, called internally.
static void ToLower(char *cDest, const char *cSrc)
void BeginFrame() override
Called at the beginning of drawing.
APIBitmap * GetAPIBitmap() const
void DrawFastDropShadow(const IRECT &innerBounds, const IRECT &outerBounds, float xyDrop, float roundness, float blur, IBlend *pBlend) override
NanoVG only.
virtual void DrawArc(const IColor &color, float cx, float cy, float r, float a1, float a2, const IBlend *pBlend=0, float thickness=1.f)
Draw an arc to the graphics context.
Definition: IGraphics.cpp:2503
void PathFill(const IPattern &pattern, const IFillOptions &options, const IBlend *pBlend) override
Fill the current current path.
std::unique_ptr< ILayer > ILayerPtr
ILayerPtr is a managed pointer for transferring the ownership of layers.
Used to store pattern information for gradients.
APIBitmap * LoadAPIBitmap(const char *fileNameOrResID, int scale, EResourceLocation location, const char *ext) override
Drawing API method to load a bitmap, called internally.
Used to store transformation matrices.
float T
Top of the rectangle (Y)
float B
Bottom of the rectangle (Y + H)