Skip to content

Commit

Permalink
Merge pull request #19 from devshareacademy/canvas-nine-slice
Browse files Browse the repository at this point in the history
Canvas nine slice demo
  • Loading branch information
scottwestover authored Nov 14, 2023
2 parents 8614ceb + 524fe5f commit 9c2e039
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 0 deletions.
3 changes: 3 additions & 0 deletions canavs-api/nine-slice-example/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["esbenp.prettier-vscode", "ritwickdey.LiveServer"]
}
12 changes: 12 additions & 0 deletions canavs-api/nine-slice-example/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"eslint.format.enable": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.singleQuote": true,
"prettier.semi": true,
"editor.formatOnSave": true,
"cSpell.words": ["bdragon", "kenneys", "tileset"],
"prettier.printWidth": 120
}
20 changes: 20 additions & 0 deletions canavs-api/nine-slice-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Canvas API - Nine Slice Demo

![demo](docs/example.gif)

A quick demo of how you can use the nine slice scaling technique with the Canvas API when drawing your images to the HTML `<canvas>` element.

For a detailed walkthrough, checkout my video on YouTube here:

[<img src="https://i.ytimg.com/vi/PJM8isAuDoI/hqdefault.jpg">](https://youtu.be/PJM8isAuDoI 'How to Draw, Scale, and Slice Images using the Canvas API')

Link to live demo:

[Demo](https://devshareacademy.github.io/code-examples-from-my-video-content/canavs-api/nine-slice-example/index.html)

## Credit

The images used in this demo were created by:

- [bdragon1727](https://bdragon1727.itch.io/border-and-panels-menu-part-1)
- [kenney](https://www.kenney.nl/assets/ui-pack-space-expansion)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://bdragon1727.itch.io/border-and-panels-menu-part-1
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

###############################################################################

UI pack: Space extension by Kenney Vleugels (www.kenney.nl)

------------------------------

License (CC0)
http://creativecommons.org/publicdomain/zero/1.0/

You may use these graphics in personal and commercial projects.
Credit (Kenney or www.kenney.nl) would be nice but is not mandatory.

###############################################################################
Binary file added canavs-api/nine-slice-example/docs/example.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions canavs-api/nine-slice-example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas API - Nine Slice Example</title>
<style>
html,
body,
.container {
margin: 0px;
height: 100vh;
width: 100vw;
overflow: hidden;
background: #d7d7d7;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
background: #000000;
box-shadow: 15px 15px 10px -5px rgba(0, 0, 0, 0.2);
image-rendering: pixelated;
zoom: 2;
}
</style>
</head>
<body>
<div class="container" id="game-container">
<canvas id="game" width="400" height="300"></canvas>
</div>
<script type="module" src="src/main.js"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions canavs-api/nine-slice-example/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "es6",
"target": "es6",
"checkJs": true
}
}
134 changes: 134 additions & 0 deletions canavs-api/nine-slice-example/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* @typedef SubRectangle
* @type {[number, number, number, number]}
*/

/**
* @param {string} src
* @returns {Promise<HTMLImageElement>}
*/
function loadImage(src) {
return new Promise((resolve) => {
const img = new Image();
img.addEventListener(
'load',
() => {
resolve(img);
},
false
);
img.src = src;
});
}

/*
A B
+---+----------------------+---+
C | 1 | 2 | 3 |
+---+----------------------+---+
| | | |
| 4 | 5 | 6 |
| | | |
+---+----------------------+---+
D | 7 | 8 | 9 |
+---+----------------------+---+
areas 1, 3, 7 and 9 (the corners) will remain unscaled
areas 2 and 8 will be stretched horizontally only
areas 4 and 6 will be stretched vertically only
area 5 will be stretched both horizontally and vertically
*/

/**
*
* @param {CanvasRenderingContext2D} ctx
* @param {HTMLImageElement} img
* @param {number} leftWidth
* @param {number} rightWidth
* @param {number} topHeight
* @param {number} bottomHeight
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @returns {void}
*/
function drawNineSliceImage(ctx, img, leftWidth, rightWidth, topHeight, bottomHeight, x, y, width, height) {
const totalWidthCut = leftWidth + rightWidth;
const totalHeightCut = topHeight + bottomHeight;
// break the main image up into 9 parts, based on the size of the slice.
// each piece should be [x, y, width, height]

// start in the top left corner for our first cut
/** @type {SubRectangle} */
const topLeft = [0, 0, leftWidth, topHeight];
// for the middle, we need to calculate the width remaining after we take our two cuts
/** @type {SubRectangle} */
const topMiddle = [leftWidth, 0, img.width - totalWidthCut, topHeight];
// for the top right side corner we just need to take the total width and remove the cut length
/** @type {SubRectangle} */
const topRight = [img.width - rightWidth, 0, rightWidth, topHeight];

// for the middle left, we take the overall image height and subtract the size of the two corner cuts to get new height
const middleRowHeight = img.height - totalHeightCut;
/** @type {SubRectangle} */
const middleLeft = [0, topHeight, leftWidth, middleRowHeight];
// for the middle, we need to take the overall image height and width, subtract the two corner cuts to get the new dimensions
/** @type {SubRectangle} */
const center = [leftWidth, topHeight, img.width - totalWidthCut, middleRowHeight];
// for the middle right, we need to do similar logic that was done for the middle left piece
/** @type {SubRectangle} */
const middleRight = [img.width - rightWidth, topHeight, rightWidth, middleRowHeight];

// for the bottom left, we take the overall image height and subtract the corner cut
const bottomRowY = img.height - bottomHeight;
/** @type {SubRectangle} */
const bottomLeft = [0, bottomRowY, leftWidth, bottomHeight];
// for the bottom middle, we do the same logic we did in the top middle frame, just at a lower y value
/** @type {SubRectangle} */
const bottomMiddle = [leftWidth, bottomRowY, img.width - totalWidthCut, bottomHeight];
// for the bottom right, we do the same logic we did in the top right frame, just at a lower y value
/** @type {SubRectangle} */
const bottomRight = [img.width - rightWidth, bottomRowY, rightWidth, bottomHeight];

// now that we have our nine sub rectangles that make up the original source image, we can draw those onto the canvas element
// drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) Note: the sx, sy, sWidth, and sHeight where already calculated above

// draw the top row
ctx.drawImage(img, ...topLeft, x, y, leftWidth, topHeight);
ctx.drawImage(img, ...topMiddle, x + leftWidth, y, width - totalWidthCut, topHeight);
ctx.drawImage(img, ...topRight, x + width - rightWidth, y, rightWidth, topHeight);
// draw the middle row
ctx.drawImage(img, ...middleLeft, x, y + topHeight, leftWidth, height - totalHeightCut);
ctx.drawImage(img, ...center, x + leftWidth, y + topHeight, width - totalWidthCut, height - totalHeightCut);
ctx.drawImage(img, ...middleRight, x + width - rightWidth, y + topHeight, rightWidth, height - totalHeightCut);
// draw the bottom row
ctx.drawImage(img, ...bottomLeft, x, y + height - bottomHeight, leftWidth, bottomHeight);
ctx.drawImage(img, ...bottomMiddle, x + leftWidth, y + height - bottomHeight, width - totalWidthCut, bottomHeight);
ctx.drawImage(img, ...bottomRight, x + width - rightWidth, y + height - bottomHeight, rightWidth, bottomHeight);
}

void (async function () {
const canvas = document.getElementById('game');
if (!(canvas instanceof HTMLCanvasElement)) {
console.log('HTMLCanvasElement not found');
return;
}
const ctx = canvas.getContext('2d');
if (!(ctx instanceof CanvasRenderingContext2D)) {
console.log('CanvasRenderingContext2D not found');
return;
}

const img = await loadImage('assets/bdragon1727/border.png');
// const img = await loadImage('assets/kenneys-assets/ui-space-expansion/glassPanel.png');

// // example of drawing image directly
// ctx.drawImage(img, 0, 0);
// // example of scaling by providing width and height on destination
// ctx.drawImage(img, 0, 0, img.width * 1.5, img.height * 1.5);
//ctx.drawImage(img, 25, 25, 350, 250);

// example of using nine slice technique to scale image
drawNineSliceImage(ctx, img, 32, 32, 32, 32, 25, 25, 350, 250);
})();

0 comments on commit 9c2e039

Please sign in to comment.