Skip to content
PixiJS Graphics & Textures — Drawing Shapes and Managing Spritesheets

PixiJS Graphics & Textures — Drawing Shapes and Managing Spritesheets

DodaTech Updated Jun 6, 2026 11 min read

PixiJS Graphics API draws vector shapes on the GPU and its texture system manages images efficiently. This tutorial shows you how to draw, cache, and reuse visuals.

What You’ll Learn

  • How the Graphics API draws shapes using GPU-accelerated commands
  • How to draw rectangles, circles, polygons, stars, and curved lines
  • The difference between BaseTexture (raw image) and Texture (region reference)
  • How sprite sheets pack many images into one texture atlas
  • How RenderTexture bakes dynamic graphics into reusable textures
  • How to build a shape drawer tool with export functionality

Why Graphics & Textures Matter

Efficient texture management is the difference between a smooth 60fps app and a stuttery mess. Every unique texture creates a GPU draw call — too many, and your frame rate drops.

At DodaTech, Durga Antivirus Pro uses sprite sheets to render security icons, threat indicators, and map markers in a single draw call. When scanning 10,000 files, the UI shows a live grid of file status icons — each one is a sprite from a shared texture atlas, rendered as a single batched operation. Without texture atlases, this visualization would crawl.

Prerequisites: Complete https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-getting-started/ first. You need to know how sprites, the stage, and the Application work.

Learning Path

    flowchart LR
    GS["Getting Started"] --> GT["Graphics & Textures ⬅ You Are Here"]
    GT --> AI["Animation & Interactivity"]
    AI --> TF["Text & Bitmap Fonts"]
    TF --> PB["Performance & Best Practices"]
    style GT fill:#4a90d9,stroke:#fff,color:#fff
    linkStyle default stroke:#4a90d9,stroke-width:2
  

PixiJS Graphics vs Canvas Shapes vs Pre-rendered Sprites

ApproachPerformanceFlexibilityUse Case
PixiJS GraphicsFast (GPU)High — vector shapesDynamic shapes, UI elements
Canvas 2D shapesSlow (CPU)HighSimple drawings, no interactivity
Pre-rendered spritesFastestLow — fixed imageIcons, backgrounds, UI

When to use Graphics: You need shapes that change at runtime (progress rings, charts, custom cursors). For static visuals, pre-render them to a texture for maximum speed.

The Graphics API — Drawing with Code

Think of the Graphics object as a digital sketchpad. You tell it what to draw (rectangle, circle, line) and how to style it (fill color, stroke), and it sends those commands directly to the GPU.

const g = new PIXI.Graphics();

// Pick a fill color and draw
g.beginFill(0xff0000);     // red fill
g.drawRect(50, 50, 200, 100); // x, y, width, height
g.endFill();               // stop filling

app.stage.addChild(g);

Why call endFill()? Every beginFill() starts a fill operation. If you don’t call endFill(), PixiJS keeps applying the fill to every shape you draw after it, which causes visual artifacts.

Drawing Methods Explained

const g = new PIXI.Graphics();

g.drawRect(x, y, w, h);           // Rectangle
g.drawRoundedRect(x, y, w, h, r); // Rectangle with rounded corners
g.drawCircle(x, y, radius);       // Perfect circle
g.drawEllipse(x, y, rx, ry);      // Oval
g.drawPolygon([x1, y1, x2, y2, ...]); // Any shape with straight edges
g.drawStar(x, y, points, outerR, innerR); // Star shape (PixiJS 7+)
g.moveTo(x, y);                   // Move pen without drawing
g.lineTo(x, y);                   // Draw a straight line
g.arc(cx, cy, r, startAngle, endAngle); // Curve an arc
g.bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY); // Smooth curved line

Why use drawPolygon for stars? Before PixiJS 7, drawStar didn’t exist. You had to manually calculate star vertex positions and pass them to drawPolygon. The built-in drawStar is both easier and faster.

Fill & Stroke — Coloring Your Shapes

const g = new PIXI.Graphics();

// Orange fill at 80% opacity
g.beginFill(0xff6600, 0.8);

// White stroke: 4px wide, full opacity
g.lineStyle(4, 0xffffff, 1);

// Circle with both fill and stroke
g.drawCircle(300, 200, 80);
g.endFill();

Line style is optional. If you don’t call lineStyle(), there’s no stroke. Call lineStyle(0) to explicitly remove the stroke.

Drawing Shapes in Practice

Star

Stars are great for UI badges, ratings, or decorative elements:

function drawStar(g, cx, cy, points, outerR, innerR) {
  g.beginFill(0xffff00);
  g.drawStar(cx, cy, points, outerR, innerR);
  g.endFill();
}

drawStar(new PIXI.Graphics(), 400, 300, 5, 100, 50);

Polygon (Custom Shape)

Polygons let you draw any shape with straight edges — triangles, diamonds, arrows:

const g = new PIXI.Graphics();
g.beginFill(0x44aa88);
g.drawPolygon([
  0, 0,
  120, 0,
  160, 60,
  120, 120,
  0, 120,
  -40, 60,
]);
g.endFill();

Textures from Images & Canvas

From an Image URL

const texture = PIXI.Texture.from('https://example.com/image.png');
const sprite = new PIXI.Sprite(texture);

From an HTML Canvas Element

You can turn anything drawn on an HTML <canvas> into a PixiJS texture:

const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ff6600';
ctx.beginPath();
ctx.arc(64, 64, 50, 0, Math.PI * 2);
ctx.fill();

const texture = PIXI.Texture.from(canvas);
const sprite = new PIXI.Sprite(texture);

Why convert a Canvas to a texture? The Canvas 2D API makes it easy to draw gradients, text, and complex patterns that Graphics doesn’t support natively. Once it’s a texture, PixiJS can display it at GPU speed.

BaseTexture vs Texture — The Two-Layer System

This is one of the most important concepts in PixiJS. Think of it like a wall and posters:

ConceptAnalogyRole
BaseTextureThe wallRaw image data stored in GPU memory
TextureA poster on the wallA reference to a specific region of the wall
// Load one large image as the "wall"
const baseTexture = PIXI.BaseTexture.from('assets/atlas.png');

// Define "posters" — regions of the wall
const texture1 = new PIXI.Texture(baseTexture, new PIXI.Rectangle(0, 0, 64, 64));
const texture2 = new PIXI.Texture(baseTexture, new PIXI.Rectangle(64, 0, 64, 64));

// Both sprites share the same GPU resource
const sprite1 = new PIXI.Sprite(texture1);
const sprite2 = new PIXI.Sprite(texture2);

Why this matters: Because texture1 and texture2 share the same BaseTexture, PixiJS can render both sprites in a single draw call. This is how sprite sheets achieve their massive performance improvement.

Warning: If you modify the BaseTexture (e.g., change its size or mipmap setting), every Texture referencing it is affected. Create a separate BaseTexture when you need independent control.

Sprite Sheets with Spritesheet

A sprite sheet (or texture atlas) is a single large image with many smaller images arranged in a grid. The accompanying JSON file tells PixiJS where each sub-image is located.

app.loader
  .add('characters', 'assets/characters.json')
  .load((loader, resources) => {
    const sheet = resources.characters.spritesheet;
    const walk1 = new PIXI.Sprite(sheet.textures['walk_01.png']);
    const walk2 = new PIXI.Sprite(sheet.textures['walk_02.png']);
  });

The JSON format looks like this:

{
  "frames": {
    "walk_01.png": { "frame": { "x": 0, "y": 0, "w": 64, "h": 64 } },
    "walk_02.png": { "frame": { "x": 64, "y": 0, "w": 64, "h": 64 } }
  },
  "meta": { "image": "characters.png" }
}

How to create sprite sheets: Use tools like TexturePacker, Shoebox, or Free Texture Packer. Export with the PixiJS preset for compatible JSON format.

RenderTexture — Baking Graphics into Textures

RenderTexture lets you take any display object (or group of objects) and “bake” it into a texture. Think of it like taking a photo of your scene and using that photo as a sticker.

const renderTexture = PIXI.RenderTexture.create({ width: 256, height: 256 });
const graphics = new PIXI.Graphics();

graphics.beginFill(0xff6600);
graphics.drawCircle(128, 128, 80);
graphics.endFill();

// "Take a photo" of the graphics
app.renderer.render(graphics, { renderTexture });

// Use the photo as a texture
const sprite = new PIXI.Sprite(renderTexture);
app.stage.addChild(sprite);

When to use RenderTexture:

  • Caching complex graphics that don’t change (saves redrawing every frame)
  • Creating composite textures from multiple elements
  • Building a paint/draw tool (render brush strokes into a texture)
  • Exporting graphics as PNG images

Texture Regions & Clipping

You can extract a sub-region of any texture:

const fullTexture = PIXI.Texture.from('assets/character.png');
const headTexture = new PIXI.Texture(
  fullTexture.baseTexture,
  new PIXI.Rectangle(10, 10, 40, 40) // x, y, width, height
);

You can also rotate a region:

headTexture.rotate = 8; // 90 degrees clockwise (see PIXI.groupD8)

Common Mistakes

MistakeWhy it happensHow to fix it
Forgetting endFill()Each beginFill() must be closedAlways pair beginFill() with endFill()
Creating new Graphics every frameGraphics rebuilds are expensiveCache static shapes in a RenderTexture
Modifying BaseTexture accidentallyChanging shared base affects all texturesCreate separate BaseTexture for independent data
Wrong rotation pivotGraphics rotates around (0,0) by defaultUse pivot.set() or wrap in a Container
Sprite sheet JSON format mismatchDifferent tools produce different JSONUse TexturePacker with the PixiJS preset
Trying to tint a Graphics objecttint works on Sprites, not GraphicsConvert to RenderTexture then use a Sprite

Practice Questions

  1. What is the difference between BaseTexture and Texture?

    • BaseTexture holds the raw image data in GPU memory. Texture is a lightweight reference to a region within it. Multiple textures can share one BaseTexture.
  2. Why is RenderTexture useful for performance?

    • It bakes a dynamic graphic into a static texture, avoiding expensive redraws every frame.
  3. What happens if you forget endFill() in a Graphics chain?

    • The fill persists and affects all subsequent shapes, causing visual artifacts.
  4. How do sprite sheets reduce draw calls?

    • All sub-images share one BaseTexture, so PixiJS renders them in a single batch.
  5. Can you draw a gradient with the Graphics API?

    • Not natively. Draw the gradient on an offscreen <canvas>, create a Texture from it, and use a Sprite.

Challenge: Build a simple pie chart using arc() and beginFill() with different colors. Display the percentage values as labels using Text objects.

FAQ

When should I use RenderTexture instead of a Graphics object?
: Use RenderTexture when a shape is static and drawn every frame (backgrounds, decals). It bakes the shape into a GPU texture, reducing draw calls.
How do I create a gradient fill in Graphics?
: PixiJS Graphics doesn’t support gradients. Draw the gradient on an offscreen <canvas>, create a Texture, and use a Sprite.
Can I tint shapes drawn with Graphics?
: Yes. Convert the shape to a RenderTexture, wrap it in a Sprite, and set sprite.tint = 0xff0000.
What is the maximum texture size?
: Depends on the GPU. Common limits: 2048, 4096, or 8192 px. Check with app.renderer.maxTextureSize.
Is drawStar available in older PixiJS versions?
: drawStar was added in PixiJS 7. In v5/v6, use drawPolygon with calculated star points.

Try It Yourself

Open this interactive shape drawer in your browser. Pick shapes, colors, and sizes, then export your creation as a PNG texture.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>PixiJS Shape Drawer</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { background: #1a1a2e; font-family: system-ui, sans-serif; display: flex; justify-content: center; padding: 20px; }
    .app { display: flex; gap: 20px; flex-wrap: wrap; }
    canvas { border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
    .panel { background: #16213e; border-radius: 12px; padding: 20px; color: #fff; width: 220px; display: flex; flex-direction: column; gap: 12px; }
    .panel h2 { font-size: 18px; }
    .panel label { font-size: 13px; color: #a8b2d1; }
    .panel input[type="range"] { width: 100%; accent-color: #4a90d9; }
    .panel input[type="color"] { width: 100%; height: 36px; border: none; border-radius: 6px; cursor: pointer; }
    .panel select { width: 100%; padding: 8px; border-radius: 6px; border: none; background: #0f3460; color: #fff; }
    .btn { padding: 10px; border: none; border-radius: 8px; background: #4a90d9; color: #fff; font-weight: 600; cursor: pointer; }
    .btn:hover { background: #357abd; }
    .btn.success { background: #2d6a4f; }
  </style>
</head>
<body>
<div class="app">
  <div id="canvas-container"></div>
  <div class="panel">
    <h2>Shape Drawer</h2>
    <label>Shape</label>
    <select id="shape">
      <option value="star" selected>Star</option>
      <option value="rect">Rectangle</option>
      <option value="circle">Circle</option>
      <option value="ellipse">Ellipse</option>
      <option value="polygon">Triangle</option>
      <option value="rounded">Rounded Rect</option>
    </select>
    <label>Fill Color</label>
    <input type="color" id="fillColor" value="#ff6600" />
    <label>Stroke Color</label>
    <input type="color" id="strokeColor" value="#ffffff" />
    <label>Stroke Width</label>
    <input type="range" id="strokeWidth" min="0" max="12" value="3" />
    <label>Rotation <span id="rotVal">0</span>°</label>
    <input type="range" id="rotation" min="0" max="360" value="0" />
    <label>Size</label>
    <input type="range" id="size" min="20" max="200" value="80" />
    <button class="btn" id="redrawBtn">Redraw</button>
    <button class="btn success" id="exportBtn">Export as PNG</button>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.x/dist/pixi.min.js"></script>
<script>
const app = new PIXI.Application({
  width: 600, height: 600,
  backgroundColor: 0x1a1a2e,
  antialias: true,
});

document.getElementById('canvas-container').appendChild(app.view);

let mainShape = new PIXI.Graphics();

function drawShape() {
  app.stage.removeChild(mainShape);
  mainShape.destroy();
  mainShape = new PIXI.Graphics();

  const shape = document.getElementById('shape').value;
  const fill = parseInt(document.getElementById('fillColor').value.slice(1), 16);
  const stroke = parseInt(document.getElementById('strokeColor').value.slice(1), 16);
  const sw = parseFloat(document.getElementById('strokeWidth').value);
  const size = parseFloat(document.getElementById('size').value);
  const deg = parseFloat(document.getElementById('rotation').value);

  if (sw > 0) mainShape.lineStyle(sw, stroke, 1);
  mainShape.beginFill(fill, 1);
  const cx = 300, cy = 300;

  switch (shape) {
    case 'rect': mainShape.drawRect(cx - size / 2, cy - size / 2, size, size); break;
    case 'circle': mainShape.drawCircle(cx, cy, size / 2); break;
    case 'ellipse': mainShape.drawEllipse(cx, cy, size / 2, size / 3); break;
    case 'polygon': mainShape.drawPolygon([cx, cy - size / 2, cx + size / 2, cy + size / 3, cx - size / 2, cy + size / 3]); break;
    case 'star': mainShape.drawStar(cx, cy, 5, size / 2, size / 5); break;
    case 'rounded': mainShape.drawRoundedRect(cx - size / 2, cy - size / 2, size, size, size / 5); break;
  }
  mainShape.endFill();
  mainShape.rotation = (deg * Math.PI) / 180;
  app.stage.addChild(mainShape);
}

document.getElementById('shape').addEventListener('change', drawShape);
document.getElementById('fillColor').addEventListener('input', drawShape);
document.getElementById('strokeColor').addEventListener('input', drawShape);
document.getElementById('strokeWidth').addEventListener('input', drawShape);
document.getElementById('size').addEventListener('input', drawShape);
document.getElementById('rotation').addEventListener('input', e => {
  document.getElementById('rotVal').textContent = e.target.value;
  drawShape();
});
document.getElementById('redrawBtn').addEventListener('click', drawShape);

document.getElementById('exportBtn').addEventListener('click', () => {
  const rt = PIXI.RenderTexture.create({ width: 600, height: 600 });
  app.renderer.render(mainShape, { renderTexture: rt });
  const canvas = app.renderer.extract.canvas(rt);
  const link = document.createElement('a');
  link.download = 'shape-texture.png';
  link.href = canvas.toDataURL('image/png');
  link.click();
  rt.destroy();
});

drawShape();

window.addEventListener('resize', () => {
  const s = Math.min(window.innerWidth - 280, 600, window.innerHeight - 40);
  app.renderer.resize(Math.max(s, 200), Math.max(s, 200));
});
</script>
</body>
</html>

What’s Next

TopicLink
Animation and interactivityhttps://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-animation-interactivity/
Text and bitmap fontshttps://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-text-fonts/
WebGL fundamentalsWebGL Explained
JavaScript graphics basicsJavaScript for Graphics

Related Tutorials

  • https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-getting-started/ — start here first
  • https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-performance/ — optimize texture batching
  • Canvas API vs WebGL — when to use each rendering approach

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Durga Antivirus Pro uses texture atlases to render thousands of threat indicators at 60fps — the techniques you learned here power real security tools.

What’s Next

Congratulations on completing this Pixijs Graphics Textures tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro