Painting a masterpiece
The art studio in Jamaa has four rooms: painting, grid drawing, pottery and prints. phantom.aj.artStudio covers all four, with a sub-area for each part: canvas (the painting and grid drawing canvases), grid, painter (paints a picture for you), pottery, prints, portraits, dialogs, submissions and masterpiece. It reads what's open, hears what the player does and draws on the canvas the way the studio does. A picture Phantom paints is saved, printed and submitted like any other.
Every stroke goes through the studio's own objects and ends with its own endDraw, the call that ends a mouse stroke. Submitting uses the studio's own Submit button and uploader. Animal Jam sees the same thing it sees when somebody paints by hand.
What's open
const studio = await phantom.aj.artStudio.canvas.current();
if (studio?.kind === 'painting') {
console.log(
`${studio.width} by ${studio.height}, ${studio.tool} in ${studio.color.toString(16)}`
);
}artStudio.current() gives whichever studio is open (painting, grid, pottery, print), and open asks for one the way clicking its easel does. The server decides: only the studio's own room offers these games, and asking from the middle of Jamaa Township may be refused.
Drawing by hand
The tools are the studio's own, by name: brush, line, airbrush, rectangle, fill, ellipse, smudge and triangle in the painting studio, and pencil and fill in the grid drawing one. Brush widths are 1 to 6, the six widths on the studio's slider.
const canvas = phantom.aj.artStudio.canvas;
await canvas.setColor(0x2f6fdb);
await canvas.setBrushSize(3);
await canvas.stroke([
{ x: 80, y: 220 },
{ x: 180, y: 120 },
{ x: 280, y: 220 },
]);
await canvas.shape('ellipse', { x: 180, y: 160 }, { x: 240, y: 200 });
await canvas.fill({ x: 20, y: 20 }, { color: 0xfff3c4 });The grid drawing studio uses squares rather than pixels, so it has grid.setCells:
await phantom.aj.artStudio.grid.setCells([
{ column: 3, row: 4, color: 0xff0000 },
{ column: 4, row: 4, color: 0xff0000 },
]);canvas.clear() and canvas.undo() are the studio's Clear and its one step of undo. As in the studio, a fill on its own doesn't count as drawing: Submit, Print and Clear stay grey until you make a stroke.
Painting a picture
painter.paint takes a picture and paints it. It fits the picture to the canvas, cuts it into cells the width of the brush and reduces it to a few dozen colours picked from the picture. Each colour is one pass of the brush. The cells of that colour are drawn as flat-ended strokes so neighbours meet exactly, and the pass ends with the studio's endDraw, which snaps every pixel it changed to that colour.
const picture = await fetch('/art/cat.png').then((answer) => answer.arrayBuffer());
const run = await phantom.aj.artStudio.painter.paint(picture, {
colors: 24,
brushSize: 1,
dither: true,
speed: 'fast',
});
const result = await run.done;
console.log(result.completed ? 'painted' : `stopped after ${result.strokes} strokes`);The picture can be pixels ({ width, height, data }, which is what a canvas's getImageData returns), the bytes of a PNG or JPEG, or a base64 data: URL of one. PNGs can be read anywhere the SDK runs. For anything else, either run somewhere with its own image decoder or work out the pixels first, which a plugin panel can do with a <canvas> in three lines.
| Option | What it does |
|---|---|
colors | How many colours to paint in, 32 by default. Each colour is a pass of the brush. |
palette | Paint in these colours instead of choosing them from the picture. |
dither | Spread each cell's rounding error to its neighbours, for smoother shading. |
brushSize | 1 to 6. A wider brush is far fewer, larger cells, and finishes much sooner. |
fit | contain (the default), cover or stretch. |
background | What shows where the picture does not reach, or is see-through. White by default. |
speed | instant, fast, steady or slow: how much goes to the game at once. |
clearFirst | Clear the canvas first and leave white cells unpainted. True by default. |
paint resolves with the run as soon as painting starts. A slow painting can take minutes, so listen for progress rather than waiting:
const picture = await fetch('/art/cat.png').then((answer) => answer.arrayBuffer());
const run = await phantom.aj.artStudio.painter.paint(picture, { speed: 'steady' });
const stop = run.onProgress(({ strokes, totalStrokes }) =>
console.log(`${Math.round((strokes / totalStrokes) * 100)}%`)
);
void run.done.then(({ completed }) => console.log(completed ? 'done' : 'stopped'));
await run.stop();
stop();painter.onProgress and painter.onDone hear every painting, whoever started it.
painter.plan works it all out without drawing anything, for a preview in a panel. It returns the palette, the colour of every cell and a preview at one pixel per cell.
const picture = await fetch('/art/cat.png').then((answer) => answer.arrayBuffer());
const plan = await phantom.aj.artStudio.painter.plan(picture, {
colors: 16,
canvas: { width: 560, height: 400 },
});
console.log(`${plan.columns} by ${plan.rows} cells, ${plan.palette.length} colours`);Sending it in
There are two ways out of the studio, both its own:
const sent = await phantom.aj.artStudio.submissions.submit();
console.log(sent.outcome);
await phantom.aj.artStudio.masterpiece.open();Listen with submissions.onStart and submissions.onResult, and masterpiece.onBought for the purchase:
const stop = phantom.aj.artStudio.submissions.onResult(
({ target, outcome, masterpieceId }) =>
console.log(target, outcome === 'sent' ? (masterpieceId ?? 'accepted') : outcome)
);dialogs.ask('storage') opens the studio's save and load dialog for .ajart files on the computer. Flash only opens a file dialog after a click by the player, so it stops at the dialog. print() presses Print, which opens the computer's print dialog.
What each studio supports
| Studio | What Phantom can do |
|---|---|
painting | Everything: eight tools, six brush widths, any colour, paint a picture, undo, clear, print, storage, Submit and masterpieces. |
grid | The same, drawn a square at a time with its pencil (grid.setCells), plus its fill. |
pottery | Choose the pot (pottery.browse), take it to the colouring stage (pottery.colour) and print it. |
print | Choose the picture (prints.browse) and print it. |
Colouring a pot happens inside the pottery movie, which the client doesn't script, so there's nothing for Phantom to drive. The same goes for the studio's colour picker and tool row. They live in the interface movie and don't tell the client anything, so canvas.onBrushChange reads their values in the game a few times a second instead of hooking anything.
Permissions
"permissions": { "game": "act" }read covers what a studio holds and what happens in it. act covers drawing, painting a picture and pressing the studio's buttons, including Submit, which sends the picture to Animal Jam under the player's name. Buying a masterpiece still spends the player's diamonds through the studio's own popup, which the player answers themselves.
There's a working plugin in Phantom's repo at plugins/examples/paint-picture: pick a file, see the preview in the mapped palette, and paint it into the open studio.