11 #include "IGraphicsCanvas.h" 15 #include <type_traits> 16 #include <emscripten.h> 18 #define STB_IMAGE_IMPLEMENTATION 19 #include "stb_image.h" 20 #include "wdl_base64.h" 22 using namespace iplug;
23 using namespace igraphics;
28 extern val GetPreloadedImages();
29 extern val GetCanvas();
34 Bitmap(val imageCanvas,
const char* name,
int scale)
36 SetBitmap(
new val(imageCanvas), imageCanvas[
"width"].as<int>(), imageCanvas[
"height"].as<int>(), scale, 1.f);
39 Bitmap(
int width,
int height,
int scale,
float drawScale)
41 val canvas = val::global(
"document").call<val>(
"createElement", std::string(
"canvas"));
42 canvas.set(
"width", width);
43 canvas.set(
"height", height);
45 SetBitmap(
new val(canvas), width, height, scale, drawScale);
54 struct IGraphicsCanvas::Font
56 using FontDesc = std::remove_pointer<FontDescriptor>::type;
58 Font(FontDesc descriptor,
double ascenderRatio,
double EMRatio)
59 : mDescriptor(descriptor), mAscenderRatio(ascenderRatio), mEMRatio(EMRatio) {}
62 double mAscenderRatio;
66 static std::string GetFontString(
const char* fontName,
const char* styleName,
double size)
68 WDL_String fontString;
69 fontString.SetFormatted(FONT_LEN + 64,
"%s %lfpx %s", styleName, size, fontName);
70 return std::string(fontString.Get());
73 StaticStorage<IGraphicsCanvas::Font> IGraphicsCanvas::sFontCache;
75 #pragma mark - Utilities 78 BEGIN_IGRAPHICS_NAMESPACE
80 std::string CanvasColor(
const IColor& color,
float alpha)
83 str.SetFormatted(64,
"rgba(%d, %d, %d, %lf)", color.R, color.G, color.B, alpha * color.A / 255.0);
87 END_IGRAPHICS_NAMESPACE
92 IGraphicsCanvas::IGraphicsCanvas(
IGEditorDelegate& dlg,
int w,
int h,
int fps,
float scale)
95 StaticStorage<Font>::Accessor storage(sFontCache);
99 IGraphicsCanvas::~IGraphicsCanvas()
101 StaticStorage<Font>::Accessor storage(sFontCache);
107 val context = GetContext();
109 context.call<
void>(
"save");
110 SetCanvasBlendMode(context, pBlend);
118 context.call<
void>(
"clip");
119 context.call<
void>(
"drawImage", img, srcX * bs, srcY * bs, sr.
W(), sr.
H(), bounds.
L, bounds.
T, bounds.
W(), bounds.
H());
120 GetContext().call<
void>(
"restore");
126 GetContext().call<
void>(
"beginPath");
131 GetContext().call<
void>(
"closePath");
136 GetContext().call<
void>(
"arc", cx, cy, r, DegToRad(a1 - 90.f), DegToRad(a2 - 90.f), winding == EWinding::CCW);
141 GetContext().call<
void>(
"moveTo", x, y);
146 GetContext().call<
void>(
"lineTo", x, y);
151 GetContext().call<
void>(
"bezierCurveTo", c1x, c1y, c2x, c2y, x2, y2);
156 GetContext().call<
void>(
"quadraticCurveTo", cx, cy, x2, y2);
161 val context = GetContext();
163 switch (options.mCapOption)
165 case ELineCap::Butt: context.set(
"lineCap",
"butt");
break;
166 case ELineCap::Round: context.set(
"lineCap",
"round");
break;
167 case ELineCap::Square: context.set(
"lineCap",
"square");
break;
170 switch (options.mJoinOption)
172 case ELineJoin::Miter: context.set(
"lineJoin",
"miter");
break;
173 case ELineJoin::Round: context.set(
"lineJoin",
"round");
break;
174 case ELineJoin::Bevel: context.set(
"lineJoin",
"bevel");
break;
177 context.set(
"miterLimit", options.mMiterLimit);
179 val dashArray = val::array();
181 for (
int i = 0; i < options.mDash.GetCount(); i++)
182 dashArray.call<
void>(
"push", val(*(options.mDash.GetArray() + i)));
184 context.call<
void>(
"setLineDash", dashArray);
185 context.set(
"lineDashOffset", options.mDash.GetOffset());
186 context.set(
"lineWidth", thickness);
188 SetCanvasSourcePattern(context, pattern, pBlend);
190 context.call<
void>(
"stroke");
192 if (!options.mPreserve)
198 val context = GetContext();
199 std::string fillRule(options.mFillRule == EFillRule::Winding ?
"nonzero" :
"evenodd");
201 SetCanvasSourcePattern(context, pattern, pBlend);
203 context.call<
void>(
"fill", fillRule);
205 if (!options.mPreserve)
209 void IGraphicsCanvas::SetCanvasSourcePattern(val& context,
const IPattern& pattern,
const IBlend* pBlend)
211 SetCanvasBlendMode(context, pBlend);
213 switch (pattern.mType)
215 case EPatternType::Solid:
218 std::string colorString = CanvasColor(color,
BlendWeight(pBlend));
220 context.set(
"fillStyle", colorString);
221 context.set(
"strokeStyle", colorString);
225 case EPatternType::Linear:
226 case EPatternType::Radial:
232 val gradient = (pattern.mType == EPatternType::Linear) ?
233 context.call<val>(
"createLinearGradient", m.mTX, m.mTY, x, y) :
234 context.call<val>(
"createRadialGradient", m.mTX, m.mTY, 0.0, m.mTX, m.mTY, m.mXX);
236 for (
int i = 0; i < pattern.
NStops(); i++)
239 gradient.call<
void>(
"addColorStop", stop.mOffset, CanvasColor(stop.mColor));
242 context.set(
"fillStyle", gradient);
243 context.set(
"strokeStyle", gradient);
249 void IGraphicsCanvas::SetCanvasBlendMode(val& context,
const IBlend* pBlend)
252 context.set(
"globalCompositeOperation",
"source-over");
254 switch (pBlend->mMethod)
256 case EBlend::SrcOver: context.set(
"globalCompositeOperation",
"source-over");
break;
257 case EBlend::SrcIn: context.set(
"globalCompositeOperation",
"source-in");
break;
258 case EBlend::SrcOut: context.set(
"globalCompositeOperation",
"source-out");
break;
259 case EBlend::SrcAtop: context.set(
"globalCompositeOperation",
"source-atop");
break;
260 case EBlend::DstOver: context.set(
"globalCompositeOperation",
"destination-over");
break;
261 case EBlend::DstIn: context.set(
"globalCompositeOperation",
"destination-in");
break;
262 case EBlend::DstOut: context.set(
"globalCompositeOperation",
"destination-out");
break;
263 case EBlend::DstAtop: context.set(
"globalCompositeOperation",
"destination-atop");
break;
264 case EBlend::Add: context.set(
"globalCompositeOperation",
"lighter");
break;
265 case EBlend::XOR: context.set(
"globalCompositeOperation",
"xor");
break;
269 void IGraphicsCanvas::PrepareAndMeasureText(
const IText& text,
const char* str,
IRECT& r,
double& x,
double & y)
const 271 StaticStorage<Font>::Accessor storage(sFontCache);
272 Font* pFont = storage.Find(text.mFont);
274 assert(pFont &&
"No font found - did you forget to load it?");
276 FontDescriptor descriptor = &pFont->mDescriptor;
277 val context = GetContext();
278 std::string fontString = GetFontString(descriptor->first.Get(), descriptor->second.Get(), text.mSize * pFont->mEMRatio);
280 context.set(
"font", fontString);
282 const double textWidth = context.call<val>(
"measureText", std::string(str))[
"width"].as<double>();
283 const double textHeight = text.mSize;
284 const double ascender = pFont->mAscenderRatio * textHeight;
285 const double descender = -(1.0 - pFont->mAscenderRatio) * textHeight;
289 case EAlign::Near: x = r.
L;
break;
290 case EAlign::Center: x = r.
MW() - (textWidth / 2.0);
break;
291 case EAlign::Far: x = r.
R - textWidth;
break;
294 switch (text.mVAlign)
296 case EVAlign::Top: y = r.
T + ascender;
break;
297 case EVAlign::Middle: y = r.
MH() + descender + (textHeight / 2.0);
break;
298 case EVAlign::Bottom: y = r.
B + descender;
break;
301 r =
IRECT((
float) x, (
float) (y - ascender), (
float) (x + textWidth), (
float) (y + textHeight - ascender));
308 PrepareAndMeasureText(text, str, bounds, x, y);
309 DoMeasureTextRotation(text, r, bounds);
315 IRECT measured = bounds;
316 val context = GetContext();
319 PrepareAndMeasureText(text, str, measured, x, y);
321 DoTextRotation(text, bounds, measured);
322 context.set(
"textBaseline", std::string(
"alphabetic"));
323 SetCanvasSourcePattern(context, text.mFGColor, pBlend);
324 context.call<
void>(
"fillText", std::string(str), x, y);
325 PathTransformRestore();
328 void IGraphicsCanvas::PathTransformSetMatrix(
const IMatrix& m)
330 const double scale = GetBackingPixelScale();
333 GetContext().call<
void>(
"setTransform", t.mXX, t.mYX, t.mXY, t.mYY, t.mTX, t.mTY);
336 void IGraphicsCanvas::SetClipRegion(
const IRECT& r)
338 val context = GetContext();
339 context.call<
void>(
"restore");
340 context.call<
void>(
"save");
341 context.call<
void>(
"beginPath");
342 context.call<
void>(
"rect", r.
L, r.
T, r.
W(), r.
H());
343 context.call<
void>(
"clip");
344 context.call<
void>(
"beginPath");
351 return (strstr(extLower,
"png") !=
nullptr) || (strstr(extLower,
"jpg") !=
nullptr) || (strstr(extLower,
"jpeg") !=
nullptr);
356 return new Bitmap(GetPreloadedImages()[fileNameOrResID], fileNameOrResID + 1, scale);
366 pRGBA = stbi_load_from_memory((
const uint8_t*)pData, dataSize, &width, &height, &channels, 4);
372 DBGMSG(
"Size: %d x %d\n", width, height);
374 int rgbaSize = width * height * channels;
375 val rgbaArray = val::global(
"Uint8ClampedArray").new_(val(emscripten::typed_memory_view(rgbaSize, pRGBA)));
377 val imgData = val::global(
"ImageData").new_(rgbaArray, val(width), val(height));
378 val canvas = val::global(
"document").call<val>(
"createElement", std::string(
"canvas"));
379 canvas.set(
"width", width);
380 canvas.set(
"height", height);
382 val ctx = canvas.call<val>(
"getContext", std::string(
"2d"));
383 ctx.call<
void>(
"putImageData", imgData, 0, 0);
385 stbi_image_free(pRGBA);
387 return new Bitmap(canvas, name, scale);
392 return new Bitmap(width, height, scale, drawScale);
395 void IGraphicsCanvas::GetFontMetrics(
const char* font,
const char* style,
double& ascenderRatio,
double& EMRatio)
399 std::string fontString = GetFontString(font, style, size);
401 val document = val::global(
"document");
402 val textSpan = document.call<val>(
"createElement", std::string(
"span"));
403 textSpan.set(
"innerHTML", std::string(
"M"));
404 textSpan[
"style"].set(
"font", fontString);
406 val block = document.call<val>(
"createElement", std::string(
"div"));
407 block[
"style"].set(
"display", std::string(
"inline-block"));
408 block[
"style"].set(
"width", std::string(
"1px"));
409 block[
"style"].set(
"height", std::string(
"0px"));
411 val div = document.call<val>(
"createElement", std::string(
"div"));
412 div.call<
void>(
"appendChild", textSpan);
413 div.call<
void>(
"appendChild", block);
414 document[
"body"].call<
void>(
"appendChild", div);
416 block[
"style"].set(
"vertical-align", std::string(
"baseline"));
417 double ascent = block[
"offsetTop"].as<
double>() - textSpan[
"offsetTop"].as<double>();
418 double height = textSpan.call<val>(
"getBoundingClientRect")[
"height"].as<double>();
419 document[
"body"].call<
void>(
"removeChild", div);
421 EMRatio = size / height;
422 ascenderRatio = ascent / height;
425 bool IGraphicsCanvas::CompareFontMetrics(
const char* style,
const char* font1,
const char* font2)
427 WDL_String fontCombination;
428 fontCombination.SetFormatted(FONT_LEN * 2 + 2,
"%s, %s", font1, font2);
429 val context = GetContext();
430 std::string textString(
"@BmwdWMoqPYyzZr1234567890.+-=_~'");
433 context.set(
"font", GetFontString(font2, style, size));
434 val metrics1 = context.call<val>(
"measureText", textString);
436 context.set(
"font", GetFontString(fontCombination.Get(), style, size));
437 val metrics2 = context.call<val>(
"measureText", textString);
439 return metrics1[
"width"].as<
double>() == metrics2[
"width"].as<double>();
442 bool IGraphicsCanvas::FontExists(
const char* font,
const char* style)
444 return !CompareFontMetrics(style, font,
"monospace") || !CompareFontMetrics(style, font,
"sans-serif") || !CompareFontMetrics(style, font,
"serif");
449 StaticStorage<Font>::Accessor storage(sFontCache);
451 if (storage.Find(fontID))
454 if (!font->IsSystem())
456 IFontDataPtr data = font->GetFontData();
462 val fontData = val(typed_memory_view(data->GetSize(), data->Get()));
463 val fontFace = val::global(
"FontFace").new_(std::string(fontID), fontData);
465 FontDescriptor descriptor = font->GetDescriptor();
466 const double ascenderRatio = data->GetAscender() /
static_cast<double>(data->GetAscender() - data->GetDescender());
467 const double EMRatio = data->GetHeightEMRatio();
468 storage.Add(
new Font({descriptor->first, descriptor->second}, ascenderRatio, EMRatio), fontID);
472 fontFace.call<val>(
"load");
473 val::global(
"document")[
"fonts"].call<
void>(
"add", fontFace);
474 mLoadingFonts.push_back(fontFace);
481 FontDescriptor descriptor = font->GetDescriptor();
482 const char* fontName = descriptor->first.Get();
483 const char* styleName = descriptor->second.Get();
485 if (FontExists(fontName, styleName))
487 double ascenderRatio, EMRatio;
489 GetFontMetrics(descriptor->first.Get(), descriptor->second.Get(), ascenderRatio, EMRatio);
490 storage.Add(
new Font({descriptor->first, descriptor->second}, ascenderRatio, EMRatio), fontID);
500 for (
auto it = mLoadingFonts.begin(); it != mLoadingFonts.end(); it++)
502 std::string status = (*it)[
"status"].as<std::string>();
504 if (status.compare(
"loaded"))
506 assert(status.compare(
"error") &&
"Font didn't load");
511 mLoadingFonts.clear();
518 const APIBitmap* pBitmap = layer->GetAPIBitmap();
520 val context = pBitmap->
GetBitmap()->call<val>(
"getContext", std::string(
"2d"));
521 val imageData = context.call<val>(
"getImageData", 0, 0, pBitmap->
GetWidth(), pBitmap->
GetHeight());
522 val pixelData = imageData[
"data"];
526 if (data.GetSize() >= size)
528 unsigned char* out = data.Get();
530 for (
auto i = 0; i < size; i++)
531 out[i] = pixelData[i].as<unsigned char>();
537 const APIBitmap* pBitmap = layer->GetAPIBitmap();
540 if (mask.GetSize() >= size)
545 double x = shadow.mXOffset * scale;
546 double y = shadow.mYOffset * scale;
548 val layerContext = layerCanvas.call<val>(
"getContext", std::string(
"2d"));
549 layerContext.call<
void>(
"setTransform");
551 if (!shadow.mDrawForeground)
553 layerContext.call<
void>(
"clearRect", 0, 0, width, height);
557 val localCanvas = *localBitmap.
GetBitmap();
558 val localContext = localCanvas.call<val>(
"getContext", std::string(
"2d"));
559 val imageData = localContext.call<val>(
"createImageData", width, height);
560 val pixelData = imageData[
"data"];
561 unsigned char* in = mask.Get();
563 for (
auto i = 0; i < size; i++)
564 pixelData.set(i, in[i]);
566 localContext.call<
void>(
"putImageData", imageData, 0, 0);
567 IBlend blend(EBlend::SrcIn, shadow.mOpacity);
568 localContext.call<
void>(
"rect", 0, 0, width, height);
569 localContext.call<
void>(
"scale", scale, scale);
570 localContext.call<
void>(
"translate", -(layer->Bounds().L + shadow.mXOffset), -(layer->Bounds().T + shadow.mYOffset));
571 SetCanvasSourcePattern(localContext, shadow.mPattern, &blend);
572 localContext.call<
void>(
"fill");
574 layerContext.set(
"globalCompositeOperation",
"destination-over");
575 layerContext.call<
void>(
"drawImage", localCanvas, 0, 0, width, height, x, y, width, height);
void Scale(float scale)
Multiply each field of this IRECT by scale.
Used to manage a rectangular area, independent of draw class/platform.
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 LoadAPIFont(const char *fontID, const PlatformFontPtr &font) override
Drawing API method to load a font from a PlatformFontPtr, called internally.
IMatrix & Invert()
Changes the matrix to be the inverse of its original value.
void GetLayerBitmapData(const ILayerPtr &layer, RawBitmapData &data) override
Get the contents of a layers pixels as bitmap data.
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.
void PathClose() override
Close the path that is being specified.
void PathClear() override
Clear the stack of path drawing commands.
Used to manage color data, independent of draw class/platform.
Used to manage stroke behaviour for path based drawing back ends.
void DoDrawText(const IText &text, const char *str, const IRECT &bounds, const IBlend *pBlend) override
float R
Right side of the rectangle (X + W)
BitmapData GetBitmap() const
An editor delegate base class for a SOMETHING that uses IGraphics for it's UI.
IText is used to manage font and text/text entry style for a piece of text on the UI...
IMatrix & Translate(float x, float y)
Set the matrix for a translation transform.
void PathLineTo(float x, float y) override
Add a line to the current path from the current point to the specified location.
const IColorStop & GetStop(int idx) const
Get the IColorStop at a particular index (will crash if out of bounds)
IGraphics platform class for the web.
bool BitmapExtSupported(const char *ext) override
Checks a file extension and reports whether this drawing API supports loading that extension...
APIBitmap * LoadAPIBitmap(const char *fileNameOrResID, int scale, EResourceLocation location, const char *ext) override
Drawing API method to load a bitmap, called internally.
void PathQuadraticBezierTo(float cx, float cy, float x2, float y2) override
Add a quadratic bezier to the current path from the current point to the specified location...
float DoMeasureText(const IText &text, const char *str, IRECT &bounds) const override
float GetDrawScale() const
IMatrix & Scale(float x, float y)
Set the matrix for a scale transform.
The lowest level base class of an IGraphics context.
float BlendWeight(const IBlend *pBlend)
Helper function to extract the blend weight value from an IBlend ptr if it is valid.
Used to specify properties of a drop-shadow to a layer.
IMatrix & Transform(const IRECT &before, const IRECT &after)
bool AssetsLoaded() override
Specialized in IGraphicsCanvas drawing backend.
Used to represent a point/stop in a gradient.
void PathStroke(const IPattern &pattern, float thickness, const IStrokeOptions &options, const IBlend *pBlend) override
Stroke the current current path.
void TransformPoint(double &x, double &y, double x0, double y0) const
Transforms the point x, y.
void PathFill(const IPattern &pattern, const IFillOptions &options, const IBlend *pBlend) override
Fill the current current path.
A base class interface for a bitmap abstraction around the different drawing back end bitmap represen...
float GetDrawScale() const
float L
Left side of the rectangle (X)
static void ToLower(char *cDest, const char *cSrc)
void PathMoveTo(float x, float y) override
Move the current point in the current path.
void DrawBitmap(const IBitmap &bitmap, const IRECT &bounds, int srcX, int srcY, const IBlend *pBlend) override
Draw a bitmap (raster) image to the graphics context.
APIBitmap * GetAPIBitmap() const
std::unique_ptr< ILayer > ILayerPtr
ILayerPtr is a managed pointer for transferring the ownership of layers.
Used to store pattern information for gradients.
Used to store transformation matrices.
void PathCubicBezierTo(float c1x, float c1y, float c2x, float c2y, float x2, float y2) override
Add a cubic bezier to the current path from the current point to the specified location.
float T
Top of the rectangle (Y)
void PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding) override
Add an arc to the current path.
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...
float B
Bottom of the rectangle (Y + H)