Sprites.Media.Image.Cells

The attribute image editor and its model. Some machines cannot colour every pixel on its own: the pixels are one bit each, ink or paper, and the ink and paper colours are set for a whole block of pixels, an attribute cell. The Spectrum and the C64 hires give each 8 by 8 cell its own ink and paper; the Oric sets ink and paper with an inline control that takes a whole 6 by 1 cell and holds along the row until the next one. So an attribute image stores a full resolution one bit mask and, at cell resolution, an ink and a paper colour grid, exactly the shape the text screen editor uses, with an editable pixel mask in place of the tiled font glyphs. The model composes the generic grid primitives to resolve the screen to packed RGBA, feeds the shared tiled canvas surface, and reuses the image editor's canvas, pan and zoom and windows. Every function composes reactive values; the raw work lives in Sprites.Grid, so this module holds no Calculate.

Sprites.Media.Image.Cells

Sprites.Media.Image.Cells.Paper Number

The paper pixel, one of the two values a mask cell holds. Paper is zero, so the mask reads as a plain one bit grid and Sprites.Grid.Two.Choose picks paper where a cell is clear.

Value 0.

Sprites.Ui.Dom.Text('Paper = ', Sprites.Media.Image.Cells.Paper);
The clear pixel value.

Sprites.Media.Image.Cells.Ink Number

The ink pixel, the other value a mask cell holds. Ink is one, so a set mask cell picks the cell's ink colour.

Value 1.

Sprites.Ui.Dom.Text('Ink = ', Sprites.Media.Image.Cells.Ink);
The set pixel value.

Sprites.Media.Image.Cells.Tile Number

The tile side the resolved screen is packed into for the canvas surface. The screen is small, so the whole screen repacks on a change; the tiling is only what the surface reads.

Value 16.

Sprites.Ui.Dom.Text('Tile = ', Sprites.Media.Image.Cells.Tile);
The block side the resolved screen packs into.

Sprites.Media.Image.Cells.DefaultInk Number

The default ink colour index when a machine names none: white ink.

Value 7.

Sprites.Ui.Dom.Text('DefaultInk = ', Sprites.Media.Image.Cells.DefaultInk);
The fallback ink index.

Sprites.Media.Image.Cells.DefaultPaper Number

The default paper colour index when a machine names none: black paper.

Value 0.

Sprites.Ui.Dom.Text('DefaultPaper = ', Sprites.Media.Image.Cells.DefaultPaper);
The fallback paper index.

Sprites.Media.Image.Cells.Wide Sprites.Reactive.Value

Sprites.Media.Image.Cells.Wide(width, cellWidth)

The number of attribute cells across a screen of a pixel width, cut into cellWidth wide cells: the width over the cell width, rounded up. A composite over Sprites.Maths.

InputTypeDescription
widthNumber or Sprites.Reactive.ValueThe pixel width of the screen.
cellWidthNumber or Sprites.Reactive.ValueThe attribute cell width in pixels.

Returns a Sprites.Reactive.Value holding the cells across.

const width = Sprites.Reactive.Value(256);
const cells = Sprites.Media.Image.Cells.Wide(width, 8);
Sprites.Ui.Input.Slider(width, 8, 320);
Sprites.Ui.Dom.Text(' cells wide = ', cells);
The cells across an 8 pixel cell screen, following the width.

Sprites.Media.Image.Cells.High Sprites.Reactive.Value

Sprites.Media.Image.Cells.High(height, cellHeight)

The number of attribute cells down a screen of a pixel height, cut into cellHeight tall cells. A composite over Sprites.Maths.

InputTypeDescription
heightNumber or Sprites.Reactive.ValueThe pixel height of the screen.
cellHeightNumber or Sprites.Reactive.ValueThe attribute cell height in pixels.

Returns a Sprites.Reactive.Value holding the cells down.

const height = Sprites.Reactive.Value(192);
const cells = Sprites.Media.Image.Cells.High(height, 8);
Sprites.Ui.Input.Slider(height, 8, 200);
Sprites.Ui.Dom.Text(' cells high = ', cells);
The cells down an 8 pixel cell screen, following the height.

Sprites.Media.Image.Cells.NewDocument Object

Sprites.Media.Image.Cells.NewDocument(platform, mode, w, h, cellWidth, cellHeight, inline)

A new attribute image document: a blank one bit mask, every pixel paper, and blank ink and paper cell grids, every cell undefined so it reads the default colour. A boot factory a page hands to the store, plain data rather than a live reactive. cellWidth, cellHeight and inline are the mode's attribute shape, kept on the document so the resolve knows the cell size.

InputTypeDescription
platformStringThe machine name stored on the document.
modeStringThe attribute mode name.
wNumberThe pixel width.
hNumberThe pixel height.
cellWidthNumberThe attribute cell width in pixels.
cellHeightNumberThe attribute cell height in pixels.
inlineBooleanTrue for inline attributes, false for per cell.

Returns the plain { media, platform, mode, w, h, cellWidth, cellHeight, inline, pixels, ink, paper } document object.

const doc = Sprites.Media.Image.Cells.NewDocument('spectrum', 'Standard', 16, 16, 8, 8, false);
const mode = Sprites.Object.Field(doc, 'mode');
const w = Sprites.Object.Field(doc, 'w');
const h = Sprites.Object.Field(doc, 'h');
const pixels = Sprites.Object.Field(doc, 'pixels');
const ink = Sprites.Object.Field(doc, 'ink');
const pixelCount = Sprites.Array.Length(pixels);
const inkCount = Sprites.Array.Length(ink);
Sprites.Ui.Dom.Text(mode, ', ', w, '×', h, ', pixels ', pixelCount, ', ink cells ', inkCount);
A fresh 16 by 16 document, a full pixel mask and one 2 by 2 grid of colour cells.

Sprites.Media.Image.Cells.EffectiveColour Sprites.Reactive.Value

Sprites.Media.Image.Cells.EffectiveColour(colours, inline, fallback)

The resolved colour of every attribute cell, as a grid: for an inline machine each cell takes the nearest set colour to its left, or the default; for a per cell machine each cell takes its own colour, or the default when absent. A composite over Grid.Two.FillAcross, Grid.Two.Default and Sprites.Logic.Select, each one node over the whole grid.

InputTypeDescription
coloursObject or Sprites.Reactive.ValueA { w, h, cells } grid of colour indices, its absent cells undefined.
inlineBoolean or Sprites.Reactive.ValueTrue to fill each row across from the left, false to default per cell.
fallbackNumber or Sprites.Reactive.ValueThe colour index an absent cell takes.

Returns a Sprites.Reactive.Value holding the resolved colour grid.

const colours = Sprites.Reactive.Value({ w: 3, h: 1, cells: [2, undefined, undefined] });
const inline = Sprites.Reactive.Value(true);
const resolved = Sprites.Media.Image.Cells.EffectiveColour(colours, inline, 0);
const last = Sprites.Grid.Two.At(resolved, 2, 0);
Sprites.Ui.Input.Checkbox(inline);
Sprites.Ui.Dom.Text(' inline; last cell = ', last);
Inline, the last cell inherits colour 2 from the left; per cell, it falls to the default 0.

Sprites.Media.Image.Cells.Colourise Sprites.Reactive.Value

Sprites.Media.Image.Cells.Colourise(colours, slots)

A colour cell grid resolved to packed RGBA: each cell's palette index mapped through the slots. A composite over Sprites.Array.Lookup, one node over the whole grid.

InputTypeDescription
coloursObject or Sprites.Reactive.ValueA { w, h, cells } grid of palette indices.
slotsArray or Sprites.Reactive.ValueThe palette as packed 0xRRGGBBAA integers.

Returns a Sprites.Reactive.Value holding the { w, h, cells } grid of packed colours.

const slots = [0x000000ff, 0xff0000ff, 0x00ff00ff];
const index = Sprites.Reactive.Value(1);
const colours = Sprites.Grid.Two.Blank(1, 1, index);
const mapped = Sprites.Media.Image.Cells.Colourise(colours, slots);
const packed = Sprites.Grid.Two.At(mapped, 0, 0);
Sprites.Ui.Input.Number(index, 0, 2);
const css = Sprites.Colour.Css(packed);
Sprites.Ui.Dom.Text(' packed colour = ', css);
The index mapped through the palette to a packed colour, shown as CSS.

Sprites.Media.Image.Cells.Resolve Sprites.Reactive.Value

Sprites.Media.Image.Cells.Resolve(data, slots, defaultInk, defaultPaper)

The whole attribute image resolved to a { w, h, cells } grid of packed RGBA colours: the ink and paper grids resolved by the attribute model, coloured through the slots, stretched to pixel size, and the mask picking ink or paper per pixel. A composite over EffectiveColour, Colourise, Grid.Two.Stretch and Grid.Two.Choose, the value the canvas and the thumbnail read.

InputTypeDescription
dataObject or Sprites.Reactive.ValueThe attribute document.
slotsArray or Sprites.Reactive.ValueThe palette as packed integers.
defaultInkNumber or Sprites.Reactive.ValueThe ink index a cell with no attribute takes.
defaultPaperNumber or Sprites.Reactive.ValueThe paper index a cell with no attribute takes.

Returns a Sprites.Reactive.Value holding the resolved { w, h, cells } grid of packed colours.

const slots = [0x000000ff, 0xffffffff];
const document = Sprites.Media.Image.Cells.NewDocument('demo', 'Standard', 8, 8, 8, 8, false);
const doc = Sprites.Reactive.Binding(document);
const paint = Sprites.Reactive.Bindingundefined, () => {
  const next = Sprites.Media.Image.Cells.Paint(doc, 0, 0, 1, Sprites.Media.Image.Cells.Ink, 'ink', 1);
  Sprites.Reactive.Set(doc, next);
});
const resolved = Sprites.Media.Image.Cells.Resolve(doc, slots, 1, 0);
const pixel = Sprites.Grid.Two.At(resolved, 0, 0);
Sprites.Ui.Button.Act(paint, 'Ink the top left pixel');
const css = Sprites.Colour.Css(pixel);
Sprites.Ui.Dom.Text(' top left = ', css);
Inking the top left pixel resolves it from black paper to white ink.

Sprites.Media.Image.Cells.Display Sprites.Reactive.Value

Sprites.Media.Image.Cells.Display(data, slots, defaultInk, defaultPaper)

The attribute image resolved and packed into a tiled grid, the form the canvas surface reads. A composite over Resolve and Grid.Tiled.FromTwo.

InputTypeDescription
dataObject or Sprites.Reactive.ValueThe attribute document.
slotsArray or Sprites.Reactive.ValueThe palette as packed integers.
defaultInkNumber or Sprites.Reactive.ValueThe default ink index.
defaultPaperNumber or Sprites.Reactive.ValueThe default paper index.

Returns a Sprites.Reactive.Value holding the { w, h, tile, tiles } tiled grid.

const slots = [0x000000ff, 0xffffffff];
const document = Sprites.Media.Image.Cells.NewDocument('demo', 'Standard', 16, 16, 8, 8, false);
const doc = Sprites.Reactive.Binding(document);
const display = Sprites.Media.Image.Cells.Display(doc, slots, 1, 0);
const pixel = Sprites.Grid.Tiled.At(display, 0, 0);
const css = Sprites.Colour.Css(pixel);
Sprites.Ui.Dom.Text('top left pixel = ', css);
The resolved screen as a tiled grid; a blank paper screen reads black.

Sprites.Media.Image.Cells.Thumbnail Sprites.Reactive.Value

Sprites.Media.Image.Cells.Thumbnail(data, slots, defaultInk, defaultPaper)

A PNG data URL of the resolved screen, for the saved preview. A composite over Resolve and Sprites.Image.DataUrlFromPixels. It reads every pixel, so a page runs it at a stroke's end.

InputTypeDescription
dataObject or Sprites.Reactive.ValueThe attribute document.
slotsArray or Sprites.Reactive.ValueThe palette as packed integers.
defaultInkNumber or Sprites.Reactive.ValueThe default ink index.
defaultPaperNumber or Sprites.Reactive.ValueThe default paper index.

Returns a Sprites.Reactive.Value holding the PNG data URL string.

const slots = [0x000000ff, 0xffffffff];
const document = Sprites.Media.Image.Cells.NewDocument('demo', 'Standard', 16, 16, 8, 8, false);
const doc = Sprites.Reactive.Binding(document);
const paint = Sprites.Reactive.Bindingundefined, () => {
  const next = Sprites.Media.Image.Cells.Paint(doc, 0, 0, 8, Sprites.Media.Image.Cells.Ink, 'ink', 1);
  Sprites.Reactive.Set(doc, next);
});
const url = Sprites.Media.Image.Cells.Thumbnail(doc, slots, 1, 0);
Sprites.Ui.Button.Act(paint, 'Ink a corner block');
Sprites.Ui.Dom.Tag('img', () => {
  Sprites.Ui.Dom.Attribute('src', url);
  Sprites.Ui.Dom.Style('width', '64px');
  Sprites.Ui.Dom.Style('image-rendering', 'pixelated');
});
The resolved screen encoded to a PNG; ink a corner block and the preview follows.

Sprites.Media.Image.Cells.Summary Sprites.Reactive.Value

Sprites.Media.Image.Cells.Summary(modeName, w, h)

The meta info summary for an attribute image: its mode and pixel size as one short string. A composite over Sprites.Text.Concat and Sprites.Logic.Select, dropping the mode when there is none.

InputTypeDescription
modeNameString or Sprites.Reactive.ValueThe mode name, or undefined for none.
wNumber or Sprites.Reactive.ValueThe pixel width.
hNumber or Sprites.Reactive.ValueThe pixel height.

Returns a Sprites.Reactive.Value holding the summary string.

const width = Sprites.Reactive.Value(256);
const summary = Sprites.Media.Image.Cells.Summary('Standard', width, 192);
Sprites.Ui.Input.Slider(width, 64, 320);
Sprites.Ui.Dom.Text(' summary = ', summary);
The mode and size as one short string, following the width.

Sprites.Media.Image.Cells.Paint Sprites.Reactive.Value

Sprites.Media.Image.Cells.Paint(data, colStart, rowStart, side, maskValue, colourKey, brush)

The document with the mask filled to a value over a pixel rectangle, and the ink or paper cells the rectangle covers filled to the brush colour. So the pencil lays ink pixels and sets the touched cells' ink; the eraser lays paper pixels and sets the touched cells' paper. maskValue is the ink or paper pixel; colourKey is 'ink' or 'paper'. A composite over Grid.Two.FillRect and Object.With.

InputTypeDescription
dataObject or Sprites.Reactive.ValueThe attribute document.
colStartNumber or Sprites.Reactive.ValueThe left pixel column of the brush square.
rowStartNumber or Sprites.Reactive.ValueThe top pixel row of the brush square.
sideNumber or Sprites.Reactive.ValueThe brush square side in pixels.
maskValueNumber or Sprites.Reactive.ValueThe ink or paper pixel the mask takes.
colourKeyString or Sprites.Reactive.ValueThe colour grid to set, 'ink' or 'paper'.
brushNumber or Sprites.Reactive.ValueThe palette index the touched cells take.

Returns a Sprites.Reactive.Value holding the painted document.

const document = Sprites.Media.Image.Cells.NewDocument('demo', 'Standard', 16, 16, 8, 8, false);
const doc = Sprites.Reactive.Binding(document);
const col = Sprites.Reactive.Value(0);
const paint = Sprites.Reactive.Bindingundefined, () => {
  const next = Sprites.Media.Image.Cells.Paint(doc, col, 0, 1, Sprites.Media.Image.Cells.Ink, 'ink', 2);
  Sprites.Reactive.Set(doc, next);
});
const pixels = Sprites.Object.Field(doc, 'pixels');
const pixel = Sprites.Array.At(pixels, col);
Sprites.Ui.Input.Number(col, 0, 15);
Sprites.Ui.Button.Act(paint, 'Ink this pixel');
Sprites.Ui.Dom.Text(' pixel = ', pixel);
Inking a pixel sets its mask cell to one and inks the 8 by 8 cell it falls in.

Sprites.Media.Image.Cells.ResizeField Sprites.Reactive.Value

Sprites.Media.Image.Cells.ResizeField(data, key, oldW, oldH, width, height, pad)

One field of the document resized to a new width and height, cropping or padding with the pad value. A composite over Grid.Two.Resize, shared by the mask and the two colour grids.

InputTypeDescription
dataObject or Sprites.Reactive.ValueThe attribute document.
keyString or Sprites.Reactive.ValueThe field to resize, 'pixels', 'ink' or 'paper'.
oldWNumber or Sprites.Reactive.ValueThe field's current width in cells.
oldHNumber or Sprites.Reactive.ValueThe field's current height in cells.
widthNumber or Sprites.Reactive.ValueThe new width in cells.
heightNumber or Sprites.Reactive.ValueThe new height in cells.
padAny or Sprites.Reactive.ValueThe value for new area.

Returns a Sprites.Reactive.Value holding the resized field's cells array.

const document = Sprites.Media.Image.Cells.NewDocument('demo', 'Standard', 8, 8, 8, 8, false);
const doc = Sprites.Reactive.Binding(document);
const width = Sprites.Reactive.Value(8);
const cells = Sprites.Media.Image.Cells.ResizeField(doc, 'pixels', 8, 8, width, 8, Sprites.Media.Image.Cells.Paper);
const count = Sprites.Array.Length(cells);
Sprites.Ui.Input.Slider(width, 4, 16);
Sprites.Ui.Dom.Text(' mask pixels = ', count);
The mask field resized to a new width, its pixel count following.

Sprites.Media.Image.Cells.ResizeAll Sprites.Reactive.Value

Sprites.Media.Image.Cells.ResizeAll(data, width, height)

The same document resized to a new pixel width and height: the mask resized at pixel resolution, the ink and paper grids resized at cell resolution, so all three stay in step. A composite over ResizeField, keeping the platform, mode and attribute shape.

InputTypeDescription
dataObject or Sprites.Reactive.ValueThe attribute document.
widthNumber or Sprites.Reactive.ValueThe new pixel width.
heightNumber or Sprites.Reactive.ValueThe new pixel height.

Returns a Sprites.Reactive.Value holding the resized document.

const document = Sprites.Media.Image.Cells.NewDocument('demo', 'Standard', 16, 16, 8, 8, false);
const doc = Sprites.Reactive.Binding(document);
const width = Sprites.Reactive.Value(16);
const resized = Sprites.Media.Image.Cells.ResizeAll(doc, width, 16);
const w = Sprites.Object.Field(resized, 'w');
const inkCells = Sprites.Object.Field(resized, 'ink');
const inkCount = Sprites.Array.Length(inkCells);
Sprites.Ui.Input.Slider(width, 8, 32);
Sprites.Ui.Dom.Text(' width = ', w, ', ink cells = ', inkCount);
The pixel width and the cell grids resize together, the ink cell count following.

Sprites.Media.Image.Cells.ToolWindow Void

Sprites.Media.Image.Cells.ToolWindow(selectedToolName, fileVisible, historyVisible)

The tools window: a slim floating window docked to the stage's top left, holding the Move and Draw tool selector and the File and History window openers. A composite over the shared sprite tool parts. It builds a floating window, so the example gates it behind a button.

InputTypeDescription
selectedToolNameSprites.Reactive.ValueThe two way current tool name, 'move' or 'draw'.
fileVisibleSprites.Reactive.ValueThe File window show flag the opener sets.
historyVisibleSprites.Reactive.ValueThe History window show flag the opener sets.

Returns nothing; it builds a floating window.

const open = Sprites.Reactive.Value(false);
Sprites.Ui.Button.SetValue(open, true, 'Open the tools window');
Sprites.Reactive.If(open, () => {
  const tool = Sprites.Reactive.Value('draw');
  const fileVisible = Sprites.Reactive.Value(false);
  const historyVisible = Sprites.Reactive.Value(false);
  Sprites.Ui.Dom.Tag('div', () => {
    Sprites.Ui.Dom.Class('editor-stage');
    Sprites.Ui.Dom.Style('height', '260px');
    Sprites.Media.Image.Cells.ToolWindow(tool, fileVisible, historyVisible);
  });
  Sprites.Ui.Dom.Text(' tool = ', tool);
});
Open the window and pick Move or Draw; the tool follows.

Sprites.Media.Image.Cells.PaletteGrid Void

Sprites.Media.Image.Cells.PaletteGrid(label, brush, slots)

One labelled swatch grid of the palette bound to a colour index: the Ink or the Paper picker. label names it; brush is the two way colour index; slots is the palette. A composite over Sprites.Media.Sprite.Tool.Labelled and Sprites.Colour.Palette.

InputTypeDescription
labelString or Sprites.Reactive.ValueThe grid's label, Ink or Paper.
brushSprites.Reactive.ValueThe two way palette index a click sets.
slotsArray or Sprites.Reactive.ValueThe palette as packed integers.

Returns nothing; it builds a labelled swatch grid.

const slots = [0x000000ff, 0xff0000ff, 0x00ff00ff, 0xffffffff];
const brush = Sprites.Reactive.Value(1);
Sprites.Media.Image.Cells.PaletteGrid('Ink', brush, slots);
Sprites.Ui.Dom.Text(' ink index = ', brush);
Click a swatch and the ink index follows.

Sprites.Media.Image.Cells.Palette Void

Sprites.Media.Image.Cells.Palette(visible, inkBrush, paperBrush, slots)

The Palette window: a floating panel docked to the stage's bottom left, holding an Ink swatch grid over a Paper swatch grid, each a click setting that brush's colour index. visible is the show flag the Draw window's colour square toggles; inkBrush and paperBrush are the two way indices; slots is the palette. A composite. It builds a floating window, so the example gates it behind a button.

InputTypeDescription
visibleSprites.Reactive.ValueThe show flag the window follows.
inkBrushSprites.Reactive.ValueThe two way ink palette index.
paperBrushSprites.Reactive.ValueThe two way paper palette index.
slotsArray or Sprites.Reactive.ValueThe palette as packed integers.

Returns nothing; it builds a floating window.

const slots = [0x000000ff, 0xff0000ff, 0x00ff00ff, 0xffffffff];
const inkBrush = Sprites.Reactive.Value(3);
const paperBrush = Sprites.Reactive.Value(0);
const visible = Sprites.Reactive.Value(false);
Sprites.Ui.Button.SetValue(visible, true, 'Open the palette');
Sprites.Reactive.If(visible, () => {
  Sprites.Ui.Dom.Tag('div', () => {
    Sprites.Ui.Dom.Class('editor-stage');
    Sprites.Ui.Dom.Style('height', '320px');
    Sprites.Media.Image.Cells.Palette(visible, inkBrush, paperBrush, slots);
  });
});
Sprites.Ui.Dom.Text(' ink = ', inkBrush, ', paper = ', paperBrush);
Open the window; picking a swatch sets the ink or the paper index.

Sprites.Media.Image.Cells.Editor Void

Sprites.Media.Image.Cells.Editor(data, thumb, info, modes, optionalModeName, slots, optionalDefaultInk, optionalDefaultPaper)

The attribute image editor. Builds the canvas view on the host, the pan and zoom overlay, the tools window, the draw window, the palette window, the file and history panels, and the info window. It reuses the image editor's canvas surface and the shared sprite windows, so an attribute screen drives them the way a full colour image does. It builds interface and returns nothing; the doc fields it is given are where the edits land.

InputTypeDescription
dataSprites.Reactive.ValueThe two way attribute document.
thumbSprites.Reactive.ValueA value the editor Sets to a fresh PNG data URL at a stroke's end.
infoSprites.Reactive.ValueA value the editor Sets to a short mode and size summary.
modesArrayThe machine's image modes, each an attribute mode with allowedSizes and the attribute shape.
optionalModeNameSprites.Reactive.ValueA two way value of the current mode's name, or absent.
slotsArrayThe machine's palette as packed integers; a cell holds an index into it.
optionalDefaultInkNumberThe ink a cell shows with no attribute, or absent for the default.
optionalDefaultPaperNumberThe paper a cell shows with no attribute, or absent for the default.

Returns nothing; it builds the full editor interface. The example reads the Spectrum config plainly, then gates the editor behind a button so the windows do not cover the page on load.

const modes = Sprites.Platform.Spectrum.Image.Modes;
const slots = Sprites.Platform.Spectrum.Image.Slots;
const defaultInk = Sprites.Platform.Spectrum.Image.DefaultInk;
const defaultPaper = Sprites.Platform.Spectrum.Image.DefaultPaper;
const open = Sprites.Reactive.Value(false);
Sprites.Ui.Button.SetValue(open, true, 'Open the editor');
Sprites.Reactive.If(open, () => {
  const start = Sprites.Media.Image.Cells.NewDocument('spectrum', 'Standard', 64, 64, 8, 8, false);
  const data = Sprites.Reactive.Value(start);
  const thumb = Sprites.Reactive.Value(undefined);
  const info = Sprites.Reactive.Value(undefined);
  const modeName = Sprites.Object.Field(data, 'mode');
  Sprites.Ui.Dom.Tag('div', () => {
    Sprites.Ui.Dom.Class('editor-stage');
    Sprites.Ui.Dom.Style('height', '420px');
    Sprites.Media.Image.Cells.Editor(data, thumb, info, modes, modeName, slots, defaultInk, defaultPaper);
  });
});
Open the editor: the canvas, tools, draw, palette and info windows mount inside the stage.