diff --git a/packages/core/app/components/SideBar.js b/packages/core/app/components/SideBar.js index 0034f5ea..4d1d5441 100644 --- a/packages/core/app/components/SideBar.js +++ b/packages/core/app/components/SideBar.js @@ -29,6 +29,7 @@ export const SideBar = ({ name, exports: functionExports, iframe, mainRef, child settings: { persistRandomOnExport, showStateExport, + engine, hidePresets, hideScaleToFit, initialScaleToFit, @@ -51,6 +52,8 @@ export const SideBar = ({ name, exports: functionExports, iframe, mainRef, child const [values, setValues] = useValues(name, inputs, exportedPresets); + const showDensitySelector = engine.isRasterExport ?? false; + const handleOnChange = (e, name, value) => { setValues(name, value); }; @@ -93,6 +96,17 @@ export const SideBar = ({ name, exports: functionExports, iframe, mainRef, child useInteractiveInputs(inputs, iframe, handleOnChange); useShortcuts(handleExport); + const DensityPicker = () => ( + + ); + return ( {children} @@ -190,16 +204,19 @@ export const SideBar = ({ name, exports: functionExports, iframe, mainRef, child {!showMultipleExports ? ( - - handleExport()} - disabled={!iframeLoaded || lastRun === undefined} - > - {lastRun === undefined ? "Error" : iframeLoaded ? "Export" : "Loading content"} - - + <> + {showDensitySelector && } + + handleExport()} + disabled={!iframeLoaded || lastRun === undefined} + > + {lastRun === undefined ? "Error" : iframeLoaded ? "Export" : "Loading content"} + + + > ) : ( <> - + { * Draws a dataUrl to canvas * @param {string} dataUrl - SVG string to draw * @param {HTMLCanvasElement} canvas - A canvas element to draw into + * @param {number} ratio – the pixel density to render at */ const dataUrlToCanvas = (dataUrl, canvas, ratio = 1) => new Promise((resolve, reject) => { diff --git a/packages/create-mechanic/CHANGELOG.md b/packages/create-mechanic/CHANGELOG.md index c0e998d7..2dac5404 100644 --- a/packages/create-mechanic/CHANGELOG.md +++ b/packages/create-mechanic/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Updated canvas based templates to use the new `getCanvas` function + ## 2.0.0-beta.9 - 2022-08-12 ### Fixed diff --git a/packages/create-mechanic/function-templates/canvas-image/index.js b/packages/create-mechanic/function-templates/canvas-image/index.js index 714b59f5..a2f008b3 100644 --- a/packages/create-mechanic/function-templates/canvas-image/index.js +++ b/packages/create-mechanic/function-templates/canvas-image/index.js @@ -1,17 +1,12 @@ -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, height, text, color1, color2, radiusPercentage } = inputs; + const { canvas, ctx } = getCanvas(); const center = [width / 2, height / 2]; const radius = ((height / 2) * radiusPercentage) / 100; const angle = Math.random() * Math.PI * 2; - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - - const ctx = canvas.getContext("2d"); - - ctx.fillStyle = "#F4F4F4"; + ctx.fillStyle = '#F4F4F4'; ctx.fillRect(0, 0, width, height); ctx.fillStyle = color1; @@ -31,9 +26,9 @@ export const handler = ({ inputs, mechanic }) => { ctx.arc(center[0], center[1], radius, 0 + angle, Math.PI + angle, false); ctx.fill(); - ctx.fillStyle = "#000000"; + ctx.fillStyle = '#000000'; ctx.font = `${height / 10}px sans-serif`; - ctx.textAlign = "center"; + ctx.textAlign = 'center'; ctx.strokeText(text, width / 2, height - height / 20); ctx.fillText(text, width / 2, height - height / 20); @@ -42,29 +37,29 @@ export const handler = ({ inputs, mechanic }) => { export const inputs = { width: { - type: "number", + type: 'number', default: 400, }, height: { - type: "number", + type: 'number', default: 300, }, text: { - type: "text", - default: "mechanic", + type: 'text', + default: 'mechanic', }, color1: { - type: "color", - model: "hex", - default: "#E94225", + type: 'color', + model: 'hex', + default: '#E94225', }, color2: { - type: "color", - model: "hex", - default: "#002EBB", + type: 'color', + model: 'hex', + default: '#002EBB', }, radiusPercentage: { - type: "number", + type: 'number', default: 40, min: 0, max: 100, @@ -84,5 +79,5 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), }; diff --git a/packages/create-mechanic/function-templates/canvas-video/index.js b/packages/create-mechanic/function-templates/canvas-video/index.js index 1b22db96..81f5f975 100644 --- a/packages/create-mechanic/function-templates/canvas-video/index.js +++ b/packages/create-mechanic/function-templates/canvas-video/index.js @@ -1,19 +1,15 @@ -export const handler = async ({ inputs, mechanic }) => { +export const handler = async ({ inputs, mechanic, getCanvas }) => { const { width, height, text, color1, color2, radiusPercentage, turns } = inputs; + const { canvas, ctx } = getCanvas(); + const center = [width / 2, height / 2]; const radius = ((height / 2) * radiusPercentage) / 100; let angle = 0; - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - - const ctx = canvas.getContext("2d"); - const drawFrame = () => { - ctx.fillStyle = "#F4F4F4"; + ctx.fillStyle = '#F4F4F4'; ctx.fillRect(0, 0, width, height); ctx.fillStyle = color1; @@ -33,9 +29,9 @@ export const handler = async ({ inputs, mechanic }) => { ctx.arc(center[0], center[1], radius, 0 + angle, Math.PI + angle, false); ctx.fill(); - ctx.fillStyle = "#000000"; + ctx.fillStyle = '#000000'; ctx.font = `${height / 10}px sans-serif`; - ctx.textAlign = "center"; + ctx.textAlign = 'center'; ctx.strokeText(text, width / 2, height - height / 20); ctx.fillText(text, width / 2, height - height / 20); @@ -53,36 +49,36 @@ export const handler = async ({ inputs, mechanic }) => { export const inputs = { width: { - type: "number", + type: 'number', default: 400, }, height: { - type: "number", + type: 'number', default: 300, }, text: { - type: "text", - default: "mechanic", + type: 'text', + default: 'mechanic', }, color1: { - type: "color", - model: "hex", - default: "#E94225", + type: 'color', + model: 'hex', + default: '#E94225', }, color2: { - type: "color", - model: "hex", - default: "#002EBB", + type: 'color', + model: 'hex', + default: '#002EBB', }, radiusPercentage: { - type: "number", + type: 'number', default: 40, min: 0, max: 100, slider: true, }, turns: { - type: "number", + type: 'number', default: 3, }, }; @@ -99,6 +95,6 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), animated: true, }; diff --git a/packages/dsi-logo-maker/CHANGELOG.md b/packages/dsi-logo-maker/CHANGELOG.md index 944823fb..e651a0ec 100644 --- a/packages/dsi-logo-maker/CHANGELOG.md +++ b/packages/dsi-logo-maker/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Refactored all canvas based functions to use the new `getCanvas` functionality + ## 1.1.0 -2021-09-29 ### Changed diff --git a/packages/dsi-logo-maker/README.md b/packages/dsi-logo-maker/README.md index c6e80879..3ea7957f 100644 --- a/packages/dsi-logo-maker/README.md +++ b/packages/dsi-logo-maker/README.md @@ -2,7 +2,7 @@ ## Usage -Run: +Run the following commands: ``` npm i diff --git a/packages/dsi-logo-maker/functions/fillAnimatedCanvas/index.js b/packages/dsi-logo-maker/functions/fillAnimatedCanvas/index.js index 337039a4..03b7f9d2 100644 --- a/packages/dsi-logo-maker/functions/fillAnimatedCanvas/index.js +++ b/packages/dsi-logo-maker/functions/fillAnimatedCanvas/index.js @@ -1,21 +1,23 @@ -import { getColors } from "../../utils/graphics"; +import { getColors } from '../../utils/graphics'; import { computeBaseBricks, computeBlockGeometry, precomputeBlocks, getIndexModule, -} from "../../utils/blocks"; -import { drawBlock } from "../../utils/blocks-canvas"; +} from '../../utils/blocks'; +import { drawBlock } from '../../utils/blocks-canvas'; -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, height, logoWidth, logoRatio, duration } = inputs; + const { ctx } = getCanvas(); + const rows = 2; const cols = 13; const logoHeight = Math.floor((logoWidth / logoRatio) * rows); - const words = ["DESIGN", "SYSTEMS", "INTERNATIONAL"]; + const words = ['DESIGN', 'SYSTEMS', 'INTERNATIONAL']; - let colors = getColors("Random Flag"); + let colors = getColors('Random Flag'); const blockGeometry = computeBlockGeometry(logoWidth, logoHeight, rows, cols); const baseBricks = computeBaseBricks(words, blockGeometry.fontSize); const blocksByIndex = precomputeBlocks(blockGeometry, baseBricks); @@ -35,7 +37,7 @@ export const handler = ({ inputs, mechanic }) => { position = { ...position }; if (position.x + block.width < width) { position.x += block.width; - colors = getColors("Random Flag"); + colors = getColors('Random Flag'); brickOffset++; } else { position.x = position.x - width; @@ -43,11 +45,6 @@ export const handler = ({ inputs, mechanic }) => { } } - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext("2d"); - const draw = () => { ctx.save(); ctx.clearRect(0, 0, width, height); @@ -84,10 +81,10 @@ export const handler = ({ inputs, mechanic }) => { draw(); } if (runtime < duration) { - mechanic.frame(canvas); + mechanic.frame(); requestAnimationFrame(animationHandler); } else { - mechanic.done(canvas); + mechanic.done(); } }; @@ -96,22 +93,22 @@ export const handler = ({ inputs, mechanic }) => { export const inputs = { width: { - type: "number", + type: 'number', default: 300, min: 100, }, height: { - type: "number", + type: 'number', default: 300, min: 100, }, logoWidth: { - type: "number", + type: 'number', default: 80, min: 10, }, logoRatio: { - type: "number", + type: 'number', default: 9, max: 20, slider: true, @@ -119,7 +116,7 @@ export const inputs = { step: 1, }, duration: { - type: "number", + type: 'number', default: 5000, step: 500, min: 1000, @@ -142,6 +139,6 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), animated: true, }; diff --git a/packages/dsi-logo-maker/functions/fillCanvas/index.js b/packages/dsi-logo-maker/functions/fillCanvas/index.js index 0067625b..14f79e61 100644 --- a/packages/dsi-logo-maker/functions/fillCanvas/index.js +++ b/packages/dsi-logo-maker/functions/fillCanvas/index.js @@ -1,21 +1,23 @@ -import { getColors } from "../../utils/graphics"; +import { getColors } from '../../utils/graphics'; import { computeBaseBricks, computeBlockGeometry, precomputeBlocks, getIndexModule, -} from "../../utils/blocks"; -import { drawBlock } from "../../utils/blocks-canvas"; +} from '../../utils/blocks'; +import { drawBlock } from '../../utils/blocks-canvas'; -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, height, logoWidth, logoRatio } = inputs; + const { ctx } = getCanvas(); + const rows = 2; const cols = 13; const logoHeight = Math.floor((logoWidth / logoRatio) * rows); - const words = ["DESIGN", "SYSTEMS", "INTERNATIONAL"]; + const words = ['DESIGN', 'SYSTEMS', 'INTERNATIONAL']; - let colors = getColors("Random Flag"); + let colors = getColors('Random Flag'); const blockGeometry = computeBlockGeometry(logoWidth, logoHeight, rows, cols); const baseBricks = computeBaseBricks(words, blockGeometry.fontSize); const blocksByIndex = precomputeBlocks(blockGeometry, baseBricks); @@ -32,43 +34,38 @@ export const handler = ({ inputs, mechanic }) => { if (position.x + block.width < width) { position.x += block.width; brickOffset++; - colors = getColors("Random Flag"); + colors = getColors('Random Flag'); } else { position.x = position.x - width; position.y += block.height; } } - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext("2d"); - ctx.save(); ctx.clearRect(0, 0, width, height); blockConfigs.forEach((blockConfig) => drawBlock(ctx, blockConfig)); ctx.restore(); - mechanic.done(canvas); + mechanic.done(); }; export const inputs = { width: { - type: "number", + type: 'number', default: 300, min: 100, }, height: { - type: "number", + type: 'number', default: 300, min: 100, }, logoWidth: { - type: "number", + type: 'number', default: 80, min: 10, }, logoRatio: { - type: "number", + type: 'number', default: 9, max: 20, slider: true, @@ -93,5 +90,5 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), }; diff --git a/packages/dsi-logo-maker/functions/flagBuilderCanvas/index.js b/packages/dsi-logo-maker/functions/flagBuilderCanvas/index.js index 7ceb6963..da2b3dee 100644 --- a/packages/dsi-logo-maker/functions/flagBuilderCanvas/index.js +++ b/packages/dsi-logo-maker/functions/flagBuilderCanvas/index.js @@ -1,8 +1,8 @@ -import { getColors, flagNames } from "../../utils/graphics"; -import { buildSeeds } from "../../utils/seeds"; -import { buildBlocks } from "../../utils/flagBlocks"; +import { getColors, flagNames } from '../../utils/graphics'; +import { buildSeeds } from '../../utils/seeds'; +import { buildBlocks } from '../../utils/flagBlocks'; -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, height, @@ -17,6 +17,8 @@ export const handler = ({ inputs, mechanic }) => { seedForOffset, } = inputs; + const { ctx } = getCanvas(); + const colors = getColors(colorMode, flag, [ firstColor, secondColor, @@ -36,80 +38,74 @@ export const handler = ({ inputs, mechanic }) => { colors, }); - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - - const ctx = canvas.getContext("2d"); - blocks.forEach((block) => { ctx.fillStyle = block.color; ctx.fillRect(block.x, block.y, block.w, block.h); }); - mechanic.done(canvas); + mechanic.done(); }; export const inputs = { width: { - type: "number", + type: 'number', default: 600, }, height: { - type: "number", + type: 'number', default: 400, }, seedMode: { - type: "text", - options: ["From Text", "Set Numbers"], - default: "From Text", + type: 'text', + options: ['From Text', 'Set Numbers'], + default: 'From Text', }, seedText: { - type: "text", - default: "Seed text", - editable: (inputs) => inputs.seedMode === "From Text", + type: 'text', + default: 'Seed text', + editable: (inputs) => inputs.seedMode === 'From Text', }, seedForDistribution: { - type: "number", + type: 'number', default: 0, - editable: (inputs) => inputs.seedMode === "Set Numbers", + editable: (inputs) => inputs.seedMode === 'Set Numbers', }, seedForOffset: { - type: "number", + type: 'number', default: 0, - editable: (inputs) => inputs.seedMode === "Set Numbers", + editable: (inputs) => inputs.seedMode === 'Set Numbers', }, colorMode: { - type: "text", - options: ["Random Flag", "Pick Flag", "Custom Colors"], - default: "Random Flag", + type: 'text', + options: ['Random Flag', 'Pick Flag', 'Custom Colors'], + default: 'Random Flag', }, flag: { - type: "text", + type: 'text', options: flagNames, default: flagNames[0], - editable: (inputs) => inputs.colorMode === "Pick Flag", + editable: (inputs) => inputs.colorMode === 'Pick Flag', }, firstColor: { - type: "color", - model: "hex", - default: "#11457e", - editable: (inputs) => inputs.colorMode === "Custom Colors", + type: 'color', + model: 'hex', + default: '#11457e', + editable: (inputs) => inputs.colorMode === 'Custom Colors', }, secondColor: { - type: "color", - model: "hex", - default: "#d7141a", - editable: (inputs) => inputs.colorMode === "Custom Colors", + type: 'color', + model: 'hex', + default: '#d7141a', + editable: (inputs) => inputs.colorMode === 'Custom Colors', }, thirdColor: { - type: "color", - model: "hex", - default: "#f1f1f1", - editable: (inputs) => inputs.colorMode === "Custom Colors", + type: 'color', + model: 'hex', + default: '#f1f1f1', + editable: (inputs) => inputs.colorMode === 'Custom Colors', }, }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), }; diff --git a/packages/dsi-logo-maker/functions/logoAnimatedCanvas/index.js b/packages/dsi-logo-maker/functions/logoAnimatedCanvas/index.js index 05209149..11649595 100644 --- a/packages/dsi-logo-maker/functions/logoAnimatedCanvas/index.js +++ b/packages/dsi-logo-maker/functions/logoAnimatedCanvas/index.js @@ -1,13 +1,13 @@ -import { getColors, flagNames } from "../../utils/graphics"; +import { getColors, flagNames } from '../../utils/graphics'; import { computeBaseBricks, computeBlockGeometry, precomputeBlocks, getIndexModule, -} from "../../utils/blocks"; -import { drawBlock } from "../../utils/blocks-canvas"; +} from '../../utils/blocks'; +import { drawBlock } from '../../utils/blocks-canvas'; -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, ratio, @@ -23,9 +23,10 @@ export const handler = ({ inputs, mechanic }) => { const rows = 2; const cols = 13; - const words = ["DESIGN", "SYSTEMS", "INTERNATIONAL"]; + const words = ['DESIGN', 'SYSTEMS', 'INTERNATIONAL']; const height = Math.floor((width / ratio) * rows); + const { ctx } = getCanvas({ height }); const colors = getColors(colorMode, flag, [ firstColor, @@ -37,11 +38,6 @@ export const handler = ({ inputs, mechanic }) => { const blocksByIndex = precomputeBlocks(blockGeometry, baseBricks); const position = { x: 0, y: 0 }; - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext("2d"); - const draw = () => { const brickOffset = Math.floor(offset * blocksByIndex.length) + internalOffset; @@ -73,10 +69,10 @@ export const handler = ({ inputs, mechanic }) => { draw(); } if (runtime < duration) { - mechanic.frame(canvas); + mechanic.frame(); requestAnimationFrame(animationHandler); } else { - mechanic.done(canvas); + mechanic.done(); } }; @@ -85,11 +81,11 @@ export const handler = ({ inputs, mechanic }) => { export const inputs = { width: { - type: "number", + type: 'number', default: 500, }, ratio: { - type: "number", + type: 'number', default: 9, max: 20, slider: true, @@ -97,36 +93,36 @@ export const inputs = { step: 1, }, colorMode: { - type: "text", - options: ["Random Flag", "Pick Flag", "Custom Colors"], - default: "Random Flag", + type: 'text', + options: ['Random Flag', 'Pick Flag', 'Custom Colors'], + default: 'Random Flag', }, flag: { - type: "text", + type: 'text', options: flagNames, default: flagNames[0], - editable: (inputs) => inputs.colorMode === "Pick Flag", + editable: (inputs) => inputs.colorMode === 'Pick Flag', }, firstColor: { - type: "color", - model: "hex", - default: "#11457e", - editable: (inputs) => inputs.colorMode === "Custom Colors", + type: 'color', + model: 'hex', + default: '#11457e', + editable: (inputs) => inputs.colorMode === 'Custom Colors', }, secondColor: { - type: "color", - model: "hex", - default: "#d7141a", - editable: (inputs) => inputs.colorMode === "Custom Colors", + type: 'color', + model: 'hex', + default: '#d7141a', + editable: (inputs) => inputs.colorMode === 'Custom Colors', }, thirdColor: { - type: "color", - model: "hex", - default: "#f1f1f1", - editable: (inputs) => inputs.colorMode === "Custom Colors", + type: 'color', + model: 'hex', + default: '#f1f1f1', + editable: (inputs) => inputs.colorMode === 'Custom Colors', }, offset: { - type: "number", + type: 'number', default: 0, min: 0, max: 1, @@ -134,13 +130,13 @@ export const inputs = { slider: true, }, duration: { - type: "number", + type: 'number', default: 5000, step: 500, min: 1000, }, loops: { - type: "number", + type: 'number', default: 4, min: 1, step: 1, @@ -159,6 +155,6 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), animated: true, }; diff --git a/packages/dsi-logo-maker/functions/logoCanvas/index.js b/packages/dsi-logo-maker/functions/logoCanvas/index.js index a892390a..adc0d289 100644 --- a/packages/dsi-logo-maker/functions/logoCanvas/index.js +++ b/packages/dsi-logo-maker/functions/logoCanvas/index.js @@ -1,12 +1,12 @@ -import { getColors, flagNames } from "../../utils/graphics"; +import { getColors, flagNames } from '../../utils/graphics'; import { computeBaseBricks, computeBlockGeometry, computeBlock, -} from "../../utils/blocks"; -import { drawBlock } from "../../utils/blocks-canvas"; +} from '../../utils/blocks'; +import { drawBlock } from '../../utils/blocks-canvas'; -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, ratio, @@ -20,9 +20,11 @@ export const handler = ({ inputs, mechanic }) => { const rows = 2; const cols = 13; - const words = ["DESIGN", "SYSTEMS", "INTERNATIONAL"]; + const words = ['DESIGN', 'SYSTEMS', 'INTERNATIONAL']; const height = Math.floor((width / ratio) * rows); + const { ctx } = getCanvas({ height }); + const colors = getColors(colorMode, flag, [ firstColor, secondColor, @@ -38,29 +40,23 @@ export const handler = ({ inputs, mechanic }) => { ); const position = { x: 0, y: 0 }; - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext("2d"); - ctx.save(); ctx.clearRect(0, 0, blockGeometry.width, blockGeometry.height); drawBlock(ctx, { position, block, colors }); ctx.restore(); - mechanic.done( - canvas, - colorMode !== "Custom Colors" ? `${flag}-${offset}` : null - ); + mechanic.done({ + name: colorMode !== 'Custom Colors' ? `${flag}-${offset}` : null, + }); }; export const inputs = { width: { - type: "number", + type: 'number', default: 500, min: 100, }, ratio: { - type: "number", + type: 'number', default: 9, max: 20, slider: true, @@ -68,36 +64,36 @@ export const inputs = { step: 1, }, colorMode: { - type: "text", - options: ["Random Flag", "Pick Flag", "Custom Colors"], - default: "Random Flag", + type: 'text', + options: ['Random Flag', 'Pick Flag', 'Custom Colors'], + default: 'Random Flag', }, flag: { - type: "text", + type: 'text', options: flagNames, default: flagNames[0], - editable: (inputs) => inputs.colorMode === "Pick Flag", + editable: (inputs) => inputs.colorMode === 'Pick Flag', }, firstColor: { - type: "color", - model: "hex", - default: "#11457e", - editable: (inputs) => inputs.colorMode === "Custom Colors", + type: 'color', + model: 'hex', + default: '#11457e', + editable: (inputs) => inputs.colorMode === 'Custom Colors', }, secondColor: { - type: "color", - model: "hex", - default: "#d7141a", - editable: (inputs) => inputs.colorMode === "Custom Colors", + type: 'color', + model: 'hex', + default: '#d7141a', + editable: (inputs) => inputs.colorMode === 'Custom Colors', }, thirdColor: { - type: "color", - model: "hex", - default: "#f1f1f1", - editable: (inputs) => inputs.colorMode === "Custom Colors", + type: 'color', + model: 'hex', + default: '#f1f1f1', + editable: (inputs) => inputs.colorMode === 'Custom Colors', }, offset: { - type: "number", + type: 'number', default: 0, min: 0, max: 1, @@ -118,5 +114,5 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), }; diff --git a/packages/dsi-logo-maker/functions/playCanvas/index.js b/packages/dsi-logo-maker/functions/playCanvas/index.js index 761368c2..02d16266 100644 --- a/packages/dsi-logo-maker/functions/playCanvas/index.js +++ b/packages/dsi-logo-maker/functions/playCanvas/index.js @@ -1,18 +1,20 @@ -import { getColors } from "../../utils/graphics"; +import { getColors } from '../../utils/graphics'; import { computeBaseBricks, computeBlockGeometry, precomputeBlocks, getIndexModule, -} from "../../utils/blocks"; -import { drawBlock } from "../../utils/blocks-canvas"; +} from '../../utils/blocks'; +import { drawBlock } from '../../utils/blocks-canvas'; -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, height, allSameColors } = inputs; - const words = ["DESIGN", "SYSTEMS", "INTERNATIONAL"]; + const { ctx } = getCanvas(); + + const words = ['DESIGN', 'SYSTEMS', 'INTERNATIONAL']; const blockConfigs = []; - let colors = getColors("Random Flag"); + let colors = getColors('Random Flag'); const blockParams = [ { rows: 6, cols: 5, logoRatio: 5, logoWidth: 150, x: 0, y: 0, offset: 0 }, @@ -75,7 +77,7 @@ export const handler = ({ inputs, mechanic }) => { for (const param of blockParams) { if (!allSameColors) { - colors = getColors("Random Flag"); + colors = getColors('Random Flag'); } const { rows, cols, logoRatio, logoWidth, x, y, offset } = param; @@ -93,31 +95,26 @@ export const handler = ({ inputs, mechanic }) => { blockConfigs.push({ position, block, colors }); } - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext("2d"); - ctx.save(); ctx.clearRect(0, 0, width, height); blockConfigs.forEach((blockConfig) => drawBlock(ctx, blockConfig)); ctx.restore(); - mechanic.done(canvas); + mechanic.done(); }; export const inputs = { width: { - type: "number", + type: 'number', default: 300, min: 100, }, height: { - type: "number", + type: 'number', default: 300, min: 100, }, allSameColors: { - type: "boolean", + type: 'boolean', default: true, }, }; @@ -130,5 +127,5 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), }; diff --git a/packages/dsi-logo-maker/functions/textAndImageCanvas/index.js b/packages/dsi-logo-maker/functions/textAndImageCanvas/index.js index 8bc3e5a2..59fc85b8 100644 --- a/packages/dsi-logo-maker/functions/textAndImageCanvas/index.js +++ b/packages/dsi-logo-maker/functions/textAndImageCanvas/index.js @@ -1,12 +1,12 @@ -import { getColors } from "../../utils/graphics"; +import { getColors } from '../../utils/graphics'; import { computeBaseBricks, computeBlockGeometry, computeBlock, -} from "../../utils/blocks"; -import { drawBlock } from "../../utils/blocks-canvas"; +} from '../../utils/blocks'; +import { drawBlock } from '../../utils/blocks-canvas'; -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, ratio, @@ -18,10 +18,12 @@ export const handler = ({ inputs, mechanic }) => { image, } = inputs; - const words = text.split(" ").map((s) => s.toUpperCase()); - const colors = getColors("Custom Colors", null, colorsString.split(",")); + const words = text.split(' ').map((s) => s.toUpperCase()); + const colors = getColors('Custom Colors', null, colorsString.split(',')); const height = Math.floor((width / ratio) * rows); + const { ctx } = getCanvas({ height }); + const blockGeometry = computeBlockGeometry(width, height, rows, cols); const baseBricks = computeBaseBricks(words, blockGeometry.fontSize); @@ -32,11 +34,6 @@ export const handler = ({ inputs, mechanic }) => { ); const position = { x: image ? height : 0, y: 0 }; - const canvas = document.createElement("canvas"); - canvas.width = width + height; - canvas.height = height; - const ctx = canvas.getContext("2d"); - ctx.save(); ctx.clearRect(0, 0, blockGeometry.width, blockGeometry.height); drawBlock(ctx, { position, block, colors }); @@ -45,22 +42,22 @@ export const handler = ({ inputs, mechanic }) => { const img = new Image(); img.onload = function () { ctx.drawImage(img, 0, 0, height, height); - mechanic.done(canvas); + mechanic.done(); }; - img.src = image ? URL.createObjectURL(image) : ""; + img.src = image ? URL.createObjectURL(image) : ''; if (!image) { - mechanic.done(canvas); + mechanic.done(); } }; export const inputs = { width: { - type: "number", + type: 'number', default: 500, min: 100, }, ratio: { - type: "number", + type: 'number', default: 9, max: 20, slider: true, @@ -68,27 +65,27 @@ export const inputs = { step: 1, }, text: { - type: "text", - default: "Whatever you want", + type: 'text', + default: 'Whatever you want', }, columns: { - type: "number", + type: 'number', default: 13, min: 1, step: 1, }, rows: { - type: "number", + type: 'number', default: 2, min: 1, step: 1, }, colors: { - type: "text", - default: "#11457e,#d7141a,#f1f1f1", + type: 'text', + default: '#11457e,#d7141a,#f1f1f1', }, offset: { - type: "number", + type: 'number', default: 0, min: 0, max: 1, @@ -96,7 +93,7 @@ export const inputs = { slider: true, }, image: { - type: "image", + type: 'image', multiple: false, }, }; @@ -109,5 +106,5 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), }; diff --git a/packages/dsi-logo-maker/functions/textAnimatedCanvas/index.js b/packages/dsi-logo-maker/functions/textAnimatedCanvas/index.js index 4931c724..9bd6eed7 100644 --- a/packages/dsi-logo-maker/functions/textAnimatedCanvas/index.js +++ b/packages/dsi-logo-maker/functions/textAnimatedCanvas/index.js @@ -1,13 +1,13 @@ -import { getColors } from "../../utils/graphics"; +import { getColors } from '../../utils/graphics'; import { computeBaseBricks, computeBlockGeometry, precomputeBlocks, getIndexModule, -} from "../../utils/blocks"; -import { drawBlock } from "../../utils/blocks-canvas"; +} from '../../utils/blocks'; +import { drawBlock } from '../../utils/blocks-canvas'; -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, ratio, @@ -20,21 +20,18 @@ export const handler = ({ inputs, mechanic }) => { loops, } = inputs; - const words = text.split(" ").map((s) => s.toUpperCase()); + const words = text.split(' ').map((s) => s.toUpperCase()); const height = Math.floor((width / ratio) * rows); - const colors = getColors("Custom Colors", null, colorsString.split(",")); + const { ctx } = getCanvas({ height }); + + const colors = getColors('Custom Colors', null, colorsString.split(',')); const blockGeometry = computeBlockGeometry(width, height, rows, cols); const baseBricks = computeBaseBricks(words, blockGeometry.fontSize); const blocksByIndex = precomputeBlocks(blockGeometry, baseBricks); const position = { x: 0, y: 0 }; - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext("2d"); - const draw = () => { const brickOffset = Math.floor(offset * blocksByIndex.length) + internalOffset; @@ -66,10 +63,10 @@ export const handler = ({ inputs, mechanic }) => { draw(); } if (runtime < duration) { - mechanic.frame(canvas); + mechanic.frame(); requestAnimationFrame(animationHandler); } else { - mechanic.done(canvas); + mechanic.done(); } }; @@ -78,12 +75,12 @@ export const handler = ({ inputs, mechanic }) => { export const inputs = { width: { - type: "number", + type: 'number', default: 500, min: 100, }, ratio: { - type: "number", + type: 'number', default: 9, max: 20, slider: true, @@ -91,27 +88,27 @@ export const inputs = { step: 1, }, text: { - type: "text", - default: "Whatever you want", + type: 'text', + default: 'Whatever you want', }, columns: { - type: "number", + type: 'number', default: 13, min: 1, step: 1, }, rows: { - type: "number", + type: 'number', default: 2, min: 1, step: 1, }, colors: { - type: "text", - default: "#11457e,#d7141a,#f1f1f1", + type: 'text', + default: '#11457e,#d7141a,#f1f1f1', }, offset: { - type: "number", + type: 'number', default: 0, min: 0, max: 1, @@ -119,13 +116,13 @@ export const inputs = { slider: true, }, duration: { - type: "number", + type: 'number', default: 10000, min: 1000, step: 500, }, loops: { - type: "number", + type: 'number', default: 4, min: 1, }, @@ -143,6 +140,6 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), animated: true, }; diff --git a/packages/dsi-logo-maker/functions/textCanvas/index.js b/packages/dsi-logo-maker/functions/textCanvas/index.js index b0db17b6..0a41df2a 100644 --- a/packages/dsi-logo-maker/functions/textCanvas/index.js +++ b/packages/dsi-logo-maker/functions/textCanvas/index.js @@ -1,12 +1,12 @@ -import { getColors } from "../../utils/graphics"; +import { getColors } from '../../utils/graphics'; import { computeBaseBricks, computeBlockGeometry, computeBlock, -} from "../../utils/blocks"; -import { drawBlock } from "../../utils/blocks-canvas"; +} from '../../utils/blocks'; +import { drawBlock } from '../../utils/blocks-canvas'; -export const handler = ({ inputs, mechanic }) => { +export const handler = ({ inputs, mechanic, getCanvas }) => { const { width, ratio, @@ -17,8 +17,8 @@ export const handler = ({ inputs, mechanic }) => { offset, } = inputs; - const words = text.split(" ").map((s) => s.toUpperCase()); - const colors = getColors("Custom Colors", null, colorsString.split(",")); + const words = text.split(' ').map((s) => s.toUpperCase()); + const colors = getColors('Custom Colors', null, colorsString.split(',')); const height = Math.floor((width / ratio) * rows); const blockGeometry = computeBlockGeometry(width, height, rows, cols); @@ -31,26 +31,23 @@ export const handler = ({ inputs, mechanic }) => { ); const position = { x: 0, y: 0 }; - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext("2d"); + const { ctx } = getCanvas({ width, height }); ctx.save(); ctx.clearRect(0, 0, blockGeometry.width, blockGeometry.height); drawBlock(ctx, { position, block, colors }); ctx.restore(); - mechanic.done(canvas); + mechanic.done(); }; export const inputs = { width: { - type: "number", + type: 'number', default: 500, min: 100, }, ratio: { - type: "number", + type: 'number', default: 9, max: 20, slider: true, @@ -58,27 +55,27 @@ export const inputs = { step: 1, }, text: { - type: "text", - default: "Whatever you want", + type: 'text', + default: 'Whatever you want', }, columns: { - type: "number", + type: 'number', default: 13, min: 1, step: 1, }, rows: { - type: "number", + type: 'number', default: 2, min: 1, step: 1, }, colors: { - type: "text", - default: "#11457e,#d7141a,#f1f1f1", + type: 'text', + default: '#11457e,#d7141a,#f1f1f1', }, offset: { - type: "number", + type: 'number', default: 0, min: 0, max: 1, @@ -99,5 +96,5 @@ export const presets = { }; export const settings = { - engine: require("@mechanic-design/engine-canvas"), + engine: require('@mechanic-design/engine-canvas'), }; diff --git a/packages/engine-canvas/CHANGELOG.md b/packages/engine-canvas/CHANGELOG.md index 4c033a40..942f5163 100644 --- a/packages/engine-canvas/CHANGELOG.md +++ b/packages/engine-canvas/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added `getCanvas` function exposed to a function's handler. Using this will give users access to a canvas that scales to the current device's pixel ratio and allows to be exported at different resolutions. + ## 2.0.0-beta.0 - 2022-03-16 ### Added diff --git a/packages/engine-canvas/index.js b/packages/engine-canvas/index.js index 0a33a6e1..8cf085ca 100644 --- a/packages/engine-canvas/index.js +++ b/packages/engine-canvas/index.js @@ -1,15 +1,29 @@ -import { Mechanic } from "@mechanic-design/core"; +import { Mechanic } from '@mechanic-design/core'; -const root = document.getElementById("root"); +const root = document.getElementById('root'); export const run = (functionName, func, values, config) => { const { isPreview } = config; - root.innerHTML = ""; + root.innerHTML = ''; const mechanic = new Mechanic(func.settings, values, config); + // Preview is always going to use the pixel density of the + // screen we're currently on. + // + // Export will respect the density setting made by the user + const canvasDensity = isPreview + ? window.devicePixelRatio || 1 + : mechanic.exportDensity; + let isElAdded = false; + + // Mutable variable in global engine scope to keep track of the prepared + // canvas we hand to the user + let preparedCanvas = null; + let getCanvasCalled = false; + const onFrame = (el) => { if (!isElAdded) { isElAdded = true; @@ -36,11 +50,89 @@ export const run = (functionName, func, values, config) => { func.handler({ inputs: mechanic.values, mechanic: { - frame: onFrame, - done: onDone, + frame: (canvas) => { + if (!canvas && !preparedCanvas) { + throw new Error( + `You need to call getCanvas() before calling frame() or pass your own canvas element as an argument to frame().` + ); + } + + onFrame(preparedCanvas ?? canvas); + }, + done: ({ canvas, name = null } = {}) => { + if (!getCanvasCalled) { + console.warn(`Seems like you’re constructing your own canvas. @mechanic-design/engine-canvas actually provides you with a canvas that can automatically scale to the pixel density of your display. + +You can use it like this: +export const handler = ({ inputs, mechanic, getCanvas }) => { + const { canvas, ctx } = getCanvas({ width: 1000, height: 1000 }); + ... +`); + } + + if (!canvas && !preparedCanvas) { + throw new Error( + `You need to call getCanvas() before calling done() or pass your own canvas element as an argument to done().` + ); + } + + onDone(preparedCanvas ?? canvas, name); + }, state: mechanic.functionState, setState: onSetState, }, + getCanvas: ({ width = null, height = null } = {}) => { + const dimensions = { + width: width ?? mechanic.values.width ?? null, + height: height ?? mechanic.values.height ?? null, + }; + + if (!dimensions.width && !dimensions.height) { + throw new Error(`No width and height values were provided to the canvas. + +You can set a width and height values by passing them the getCanvas function: +getCanvas({ width: 1000, height: 1000 }); + +You can also set width and height by adding an input called width and height to your functions’ inputs. +`); + } + + if (!dimensions.width) { + throw new Error(`No width value was provided to the canvas. + +You can set a width value by passing it the getCanvas function: +getCanvas({ width: 1000 }); + +You can also set a width by adding an input called width to your functions’ inputs. +`); + } + + if (!dimensions.height) { + throw new Error(`No height value was provided to the canvas. + +You can set a height value by passing it the getCanvas function: +getCanvas({ height: 1000 }); + +You can also set a height by adding an input called height to your functions’ inputs. +`); + } + + preparedCanvas = document.createElement('canvas'); + preparedCanvas.width = dimensions.width * canvasDensity; + preparedCanvas.height = dimensions.height * canvasDensity; + preparedCanvas.style.width = `${dimensions.width}px`; + preparedCanvas.style.height = `${dimensions.height}px`; + + const ctx = preparedCanvas.getContext('2d'); + + ctx.scale(canvasDensity, canvasDensity); + + getCanvasCalled = true; + + return { canvas: preparedCanvas, ctx }; + }, }); return mechanic; }; + +export const isRasterExport = true;