11#include "IGraphicsCanvas.h"
16#include <emscripten.h>
18#define STB_IMAGE_IMPLEMENTATION
20#include "wdl_base64.h"
23using namespace igraphics;
24using namespace emscripten;
28extern val GetPreloadedImages();
29extern 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);
54struct 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;
66static 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());
73StaticStorage<IGraphicsCanvas::Font> IGraphicsCanvas::sFontCache;
75#pragma mark - Utilities
78BEGIN_IGRAPHICS_NAMESPACE
80std::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);
87END_IGRAPHICS_NAMESPACE
92IGraphicsCanvas::IGraphicsCanvas(
IGEditorDelegate& dlg,
int w,
int h,
int fps,
float scale)
95 StaticStorage<Font>::Accessor storage(sFontCache);
99IGraphicsCanvas::~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)
209void 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);
251void IGraphicsCanvas::SetCanvasBlendMode(val& context,
const IBlend* pBlend)
254 context.set(
"globalCompositeOperation",
"source-over");
256 switch (pBlend->mMethod)
258 case EBlend::SrcOver: context.set(
"globalCompositeOperation",
"source-over");
break;
259 case EBlend::SrcIn: context.set(
"globalCompositeOperation",
"source-in");
break;
260 case EBlend::SrcOut: context.set(
"globalCompositeOperation",
"source-out");
break;
261 case EBlend::SrcAtop: context.set(
"globalCompositeOperation",
"source-atop");
break;
262 case EBlend::DstOver: context.set(
"globalCompositeOperation",
"destination-over");
break;
263 case EBlend::DstIn: context.set(
"globalCompositeOperation",
"destination-in");
break;
264 case EBlend::DstOut: context.set(
"globalCompositeOperation",
"destination-out");
break;
265 case EBlend::DstAtop: context.set(
"globalCompositeOperation",
"destination-atop");
break;
266 case EBlend::Add: context.set(
"globalCompositeOperation",
"lighter");
break;
267 case EBlend::XOR: context.set(
"globalCompositeOperation",
"xor");
break;
271void IGraphicsCanvas::PrepareAndMeasureText(
const IText& text,
const char* str,
IRECT& r,
double& x,
double & y)
const
273 StaticStorage<Font>::Accessor storage(sFontCache);
274 Font* pFont = storage.Find(text.mFont);
276 assert(pFont &&
"No font found - did you forget to load it?");
278 FontDescriptor descriptor = &pFont->mDescriptor;
279 val context = GetContext();
280 std::string fontString = GetFontString(descriptor->first.Get(), descriptor->second.Get(), text.mSize * pFont->mEMRatio);
282 context.set(
"font", fontString);
284 const double textWidth = context.call<val>(
"measureText", std::string(str))[
"width"].as<double>();
285 const double textHeight = text.mSize;
286 const double ascender = pFont->mAscenderRatio * textHeight;
287 const double descender = -(1.0 - pFont->mAscenderRatio) * textHeight;
291 case EAlign::Near: x = r.L;
break;
292 case EAlign::Center: x = r.
MW() - (textWidth / 2.0);
break;
293 case EAlign::Far: x = r.R - textWidth;
break;
296 switch (text.mVAlign)
298 case EVAlign::Top: y = r.T + ascender;
break;
299 case EVAlign::Middle: y = r.
MH() + descender + (textHeight / 2.0);
break;
300 case EVAlign::Bottom: y = r.B + descender;
break;
303 r =
IRECT((
float) x, (
float) (y - ascender), (
float) (x + textWidth), (
float) (y + textHeight - ascender));
310 PrepareAndMeasureText(text, str, bounds, x, y);
317 IRECT measured = bounds;
318 val context = GetContext();
321 PrepareAndMeasureText(text, str, measured, x, y);
323 DoTextRotation(text, bounds, measured);
324 context.set(
"textBaseline", std::string(
"alphabetic"));
325 SetCanvasSourcePattern(context, text.mFGColor, pBlend);
326 context.call<
void>(
"fillText", std::string(str), x, y);
330void IGraphicsCanvas::PathTransformSetMatrix(
const IMatrix& m)
335 GetContext().call<
void>(
"setTransform", t.mXX, t.mYX, t.mXY, t.mYY, t.mTX, t.mTY);
338void IGraphicsCanvas::SetClipRegion(
const IRECT& r)
340 val context = GetContext();
341 context.call<
void>(
"restore");
342 context.call<
void>(
"save");
343 context.call<
void>(
"beginPath");
344 context.call<
void>(
"rect", r.L, r.T, r.
W(), r.
H());
345 context.call<
void>(
"clip");
346 context.call<
void>(
"beginPath");
353 return (strstr(extLower,
"png") !=
nullptr) || (strstr(extLower,
"jpg") !=
nullptr) || (strstr(extLower,
"jpeg") !=
nullptr);
358 return new Bitmap(GetPreloadedImages()[fileNameOrResID], fileNameOrResID + 1, scale);
368 pRGBA = stbi_load_from_memory((
const uint8_t*)pData, dataSize, &width, &height, &channels, 4);
374 DBGMSG(
"Size: %d x %d\n", width, height);
376 int rgbaSize = width * height * channels;
377 val rgbaArray = val::global(
"Uint8ClampedArray").new_(val(emscripten::typed_memory_view(rgbaSize, pRGBA)));
379 val imgData = val::global(
"ImageData").new_(rgbaArray, val(width), val(height));
380 val canvas = val::global(
"document").call<val>(
"createElement", std::string(
"canvas"));
381 canvas.set(
"width", width);
382 canvas.set(
"height", height);
384 val ctx = canvas.call<val>(
"getContext", std::string(
"2d"));
385 ctx.call<
void>(
"putImageData", imgData, 0, 0);
387 stbi_image_free(pRGBA);
389 return new Bitmap(canvas, name, scale);
394 return new Bitmap(width, height, scale, drawScale);
397void IGraphicsCanvas::GetFontMetrics(
const char* font,
const char* style,
double& ascenderRatio,
double& EMRatio)
401 std::string fontString = GetFontString(font, style, size);
403 val document = val::global(
"document");
404 val textSpan = document.call<val>(
"createElement", std::string(
"span"));
405 textSpan.set(
"innerHTML", std::string(
"M"));
406 textSpan[
"style"].set(
"font", fontString);
408 val block = document.call<val>(
"createElement", std::string(
"div"));
409 block[
"style"].set(
"display", std::string(
"inline-block"));
410 block[
"style"].set(
"width", std::string(
"1px"));
411 block[
"style"].set(
"height", std::string(
"0px"));
413 val div = document.call<val>(
"createElement", std::string(
"div"));
414 div.call<
void>(
"appendChild", textSpan);
415 div.call<
void>(
"appendChild", block);
416 document[
"body"].call<
void>(
"appendChild", div);
418 block[
"style"].set(
"vertical-align", std::string(
"baseline"));
419 double ascent = block[
"offsetTop"].as<
double>() - textSpan[
"offsetTop"].as<double>();
420 double height = textSpan.call<val>(
"getBoundingClientRect")[
"height"].as<double>();
421 document[
"body"].call<
void>(
"removeChild", div);
423 EMRatio = size / height;
424 ascenderRatio = ascent / height;
427bool IGraphicsCanvas::CompareFontMetrics(
const char* style,
const char* font1,
const char* font2)
429 WDL_String fontCombination;
430 fontCombination.SetFormatted(FONT_LEN * 2 + 2,
"%s, %s", font1, font2);
431 val context = GetContext();
432 std::string textString(
"@BmwdWMoqPYyzZr1234567890.+-=_~'");
435 context.set(
"font", GetFontString(font2, style, size));
436 val metrics1 = context.call<val>(
"measureText", textString);
438 context.set(
"font", GetFontString(fontCombination.Get(), style, size));
439 val metrics2 = context.call<val>(
"measureText", textString);
441 return metrics1[
"width"].as<
double>() == metrics2[
"width"].as<double>();
444bool IGraphicsCanvas::FontExists(
const char* font,
const char* style)
446 return !CompareFontMetrics(style, font,
"monospace") || !CompareFontMetrics(style, font,
"sans-serif") || !CompareFontMetrics(style, font,
"serif");
451 StaticStorage<Font>::Accessor storage(sFontCache);
453 if (storage.Find(fontID))
456 if (!font->IsSystem())
458 IFontDataPtr data = font->GetFontData();
464 val fontData = val(typed_memory_view(data->GetSize(), data->Get()));
465 val fontFace = val::global(
"FontFace").new_(std::string(fontID), fontData);
467 FontDescriptor descriptor = font->GetDescriptor();
468 const double ascenderRatio = data->GetAscender() /
static_cast<double>(data->GetAscender() - data->GetDescender());
469 const double EMRatio = data->GetHeightEMRatio();
470 storage.Add(
new Font({descriptor->first, descriptor->second}, ascenderRatio, EMRatio), fontID);
474 fontFace.call<val>(
"load");
475 val::global(
"document")[
"fonts"].call<
void>(
"add", fontFace);
476 mLoadingFonts.push_back(fontFace);
483 FontDescriptor descriptor = font->GetDescriptor();
484 const char* fontName = descriptor->first.Get();
485 const char* styleName = descriptor->second.Get();
487 if (FontExists(fontName, styleName))
489 double ascenderRatio, EMRatio;
491 GetFontMetrics(descriptor->first.Get(), descriptor->second.Get(), ascenderRatio, EMRatio);
492 storage.Add(
new Font({descriptor->first, descriptor->second}, ascenderRatio, EMRatio), fontID);
502 for (
auto it = mLoadingFonts.begin(); it != mLoadingFonts.end(); it++)
504 std::string status = (*it)[
"status"].as<std::string>();
506 if (status.compare(
"loaded"))
508 assert(status.compare(
"error") &&
"Font didn't load");
513 mLoadingFonts.clear();
520 const APIBitmap* pBitmap = layer->GetAPIBitmap();
522 val context = pBitmap->
GetBitmap()->call<val>(
"getContext", std::string(
"2d"));
523 val imageData = context.call<val>(
"getImageData", 0, 0, pBitmap->
GetWidth(), pBitmap->
GetHeight());
524 val pixelData = imageData[
"data"];
528 if (data.GetSize() >= size)
530 unsigned char* out = data.Get();
532 for (
auto i = 0; i < size; i++)
533 out[i] = pixelData[i].as<unsigned char>();
539 const APIBitmap* pBitmap = layer->GetAPIBitmap();
542 if (mask.GetSize() >= size)
547 double x = shadow.mXOffset * scale;
548 double y = shadow.mYOffset * scale;
550 val layerContext = layerCanvas.call<val>(
"getContext", std::string(
"2d"));
551 layerContext.call<
void>(
"setTransform");
553 if (!shadow.mDrawForeground)
555 layerContext.call<
void>(
"clearRect", 0, 0, width, height);
559 val localCanvas = *localBitmap.
GetBitmap();
560 val localContext = localCanvas.call<val>(
"getContext", std::string(
"2d"));
561 val imageData = localContext.call<val>(
"createImageData", width, height);
562 val pixelData = imageData[
"data"];
563 unsigned char* in = mask.Get();
565 for (
auto i = 0; i < size; i++)
566 pixelData.set(i, in[i]);
568 localContext.call<
void>(
"putImageData", imageData, 0, 0);
569 IBlend blend(EBlend::SrcIn, shadow.mOpacity);
570 localContext.call<
void>(
"rect", 0, 0, width, height);
571 localContext.call<
void>(
"scale", scale, scale);
572 localContext.call<
void>(
"translate", -(layer->Bounds().L + shadow.mXOffset), -(layer->Bounds().T + shadow.mYOffset));
573 SetCanvasSourcePattern(localContext, shadow.mPattern, &blend);
574 localContext.call<
void>(
"fill");
576 layerContext.set(
"globalCompositeOperation",
"destination-over");
577 layerContext.call<
void>(
"drawImage", localCanvas, 0, 0, width, height, x, y, width, height);
A base class interface for a bitmap abstraction around the different drawing back end bitmap represen...
BitmapData GetBitmap() const
void SetBitmap(BitmapData pBitmap, int w, int h, float scale, float drawScale)
Used to initialise the members after construction.
float GetDrawScale() const
User-facing bitmap abstraction that you use to manage bitmap data, independant of draw class/platform...
float GetDrawScale() const
APIBitmap * GetAPIBitmap() const
An editor delegate base class that uses IGraphics for the UI.
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.
bool LoadAPIFont(const char *fontID, const PlatformFontPtr &font) override
Drawing API method to load a font from a PlatformFontPtr, called internally.
bool BitmapExtSupported(const char *ext) override
Checks a file extension and reports whether this drawing API supports loading that extension.
float DoMeasureText(const IText &text, const char *str, IRECT &bounds) const override
void PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding) override
Add an arc to the current path.
APIBitmap * LoadAPIBitmap(const char *fileNameOrResID, int scale, EResourceLocation location, const char *ext) override
Drawing API method to load a bitmap, called internally.
void PathClear() override
Clear the stack of path drawing commands.
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.
void PathClose() override
Close the path that is being specified.
void PathMoveTo(float x, float y) override
Move the current point in the current path.
void PathFill(const IPattern &pattern, const IFillOptions &options, const IBlend *pBlend) override
Fill the current current path.
bool AssetsLoaded() override
Specialized in IGraphicsCanvas drawing backend.
void PathLineTo(float x, float y) override
Add a line to the current path from the current point to the specified location.
void PathStroke(const IPattern &pattern, float thickness, const IStrokeOptions &options, const IBlend *pBlend) override
Stroke the current current path.
APIBitmap * CreateAPIBitmap(int width, int height, float scale, double drawScale, bool cacheable=false) override
Creates a new API bitmap, either in memory or as a GPU texture.
void GetLayerBitmapData(const ILayerPtr &layer, RawBitmapData &data) override
Get the contents of a layer as Raw RGBA bitmap data NOTE: you should only call this within IControl::...
void DoDrawText(const IText &text, const char *str, const IRECT &bounds, const IBlend *pBlend) override
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 DrawBitmap(const IBitmap &bitmap, const IRECT &bounds, int srcX, int srcY, const IBlend *pBlend) override
Draw a bitmap (raster) image to the graphics context.
The lowest level base class of an IGraphics context.
void PathRect(const IRECT &bounds)
Add a rectangle to the current path.
virtual float GetBackingPixelScale() const
void DoMeasureTextRotation(const IText &text, const IRECT &bounds, IRECT &rect) const
void PathTransformRestore()
Restore the affine transform of the current path, to the previously saved state.
void PathTransformSave()
Save the current affine transform of the current path.
IGraphics platform class for the web.
std::unique_ptr< ILayer > ILayerPtr
ILayerPtr is a managed pointer for transferring the ownership of layers.
float BlendWeight(const IBlend *pBlend)
Helper function to extract the blend weight value from an IBlend ptr if it is valid.
Used to manage stroke behaviour for path based drawing back ends.
static void ToLower(char *cDest, const char *cSrc)
Used to manage composite/blend operations, independent of draw class/platform.
Used to manage color data, independent of draw class/platform.
Used to represent a point/stop in a gradient.
Used to manage fill behaviour.
Used to store transformation matrices.
IMatrix & Scale(float x, float y)
Set the matrix for a scale transform.
IMatrix & Invert()
Changes the matrix to be the inverse of its original value.
IMatrix & Transform(const IRECT &before, const IRECT &after)
IMatrix & Translate(float x, float y)
Set the matrix for a translation transform.
void TransformPoint(double &x, double &y, double x0, double y0) const
Transforms the point x, y.
Used to store pattern information for gradients.
const IColorStop & GetStop(int idx) const
Get the IColorStop at a particular index (will crash if out of bounds)
Used to manage a rectangular area, independent of draw class/platform.
void Scale(float scale)
Multiply each field of this IRECT by scale.
Used to specify properties of a drop-shadow to a layer.
IText is used to manage font and text/text entry style for a piece of text on the UI,...