Skip to content

episode 004

OGAWA Keiji edited this page Aug 30, 2026 · 1 revision

Paste an Image into Canvas

It's finally time to start implementing okoze itself.

Overall plan

v0.1.0 will support Sudoku.

Here is the list of features planned for this version. For details, see docs/features.md.

🟧 ID Description Done Notes
🟧 1A Development environment 🟢
🟧 1B Basic puzzle play
🟧 1C Saving and resuming puzzles
🟧 1D Undo/redo
🟧 1E Sudoku rules
🟧 1F Built-in solving techniques
🟧 1G Solver

Development Workflow

From this episode onward, I will develop each feature on a separate branch and use tests to track its implementation.

See docs/development.md for details.

Unfinished tests from the previous episode

A couple of tests from the previous episode are still unfinished.

I am not going to work on them yet. I have recorded them in docs/features.md so they will not be forgotten.

Note

Web development is not my strong point. Rather than spending too much time in an area I am less familiar with, I would rather move on to something I know better and make progress there first.

The unfinished tests are:

🟧 1B1 Canvas positioning
🔴 The Canvas can fill the available window
🔴 The Canvas can be resized when the window size changes

I will come back to these tests later.

This episode

Here is what I'll implement this time.

🟧 1B2 Image import and display adjustment
🔴 When "paste" is executed, the clipboard contents are pasted onto the canvas.
🔴 Nothing happens if the clipboard does not contain an image.
🔴 The image is fit to the canvas size (the image is automatically scaled up or down; the canvas itself is not scaled).
🔴 The image's aspect ratio is preserved.

🔴 When "paste" is executed, the clipboard contents are pasted onto the canvas.

Test procedure

  • Take a screenshot and put it on the clipboard (on a Mac, press ⌘+Shift+5).
  • Make okoze active and paste it (on a Mac, press ⌘+V).
  • Check that the screenshot is displayed on the canvas.

Implementation

  • When "paste" is executed, place the clipboard contents on the canvas.
  • Place it at the origin.
  • Use a scale of 1.

I don't know much about the Canvas API yet, but this should be enough.

I'll have AI generate the code for me.

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
document.addEventListener("paste", (event) => {
    const item = event.clipboardData.items[0];
    const file = item.getAsFile();
    if (file) {
        const image = new Image();
        image.onload = () => {
            ctx.drawImage(image, 0, 0);
        };
        image.src = URL.createObjectURL(file);
    }
});

I didn't really understand the code, so I asked AI a few questions. Here are the answers in brief. If you want more details, ask AI.

  • Is the clipboard an array? Data is stored by type, so there can be multiple items.
  • What does getAsFile() return? A File object is returned for a screenshot.
  • Why doesn't the image disappear when the canvas is redrawn? Because the Canvas keeps the pixels that have been drawn.
  • Is the clipboard mechanism independent of the browser and OS? The Web API abstracts away the differences.
  • Where can I find the API documentation? MDN Web API

The test passes.

🟢 When "paste" is executed, the clipboard contents are pasted onto the canvas.

🔴 Nothing happens if the clipboard does not contain an image.

Nothing happens when I copy and paste text. Is this because of the if (file) check?

I asked AI just to make sure. Apparently, getAsFile() can return a File when a regular file is copied as well.

Copying and pasting a file doesn't cause anything to happen in my test, but I'll make the check more explicit just to be safe.

if (item.type.startsWith("image/")) {

🟢 Nothing happens if the clipboard does not contain an image.

🔴 The image is fit to the canvas size (the image is automatically scaled up or down; the canvas itself is not scaled).

I asked AI to implement this.

ctx.drawImage(image, 0, 0, canvas.width, canvas.height);

It seems to work correctly.

Tip

It is a good idea to test both a small image and a large image.

The scaling algorithm and how pixels at the edges are handled are interesting details, but this is not an image-processing application. If the result looks fine, that is good enough for now.

🟢 The image is fit to the canvas size (the image is automatically scaled up or down; the canvas itself is not scaled).

🔴 The image's aspect ratio is preserved.

I asked AI to implement this.

const scale = Math.min(
    canvas.width / image.width,
    canvas.height / image.height
);
const width = image.width * scale;
const height = image.height * scale;
ctx.drawImage(image, 0, 0, width, height);

It seems to work correctly.

Tip

It is a good idea to test both a portrait image and a landscape image.

🟢 The image's aspect ratio is preserved.

The image is now aligned to one side, so let's center it.

🔴 The image is centered on the canvas.

I asked AI to implement this.

const xPos = (canvas.width - width) / 2;
const yPos = (canvas.height - height) / 2;
ctx.drawImage(image, xPos, yPos, width, height);

It works.

🟢 The image is centered on the canvas.

Note

When we eventually need to import very large puzzles, we may need features for zooming and moving the image.

One option would be to add these to the TODO list. However, I won't register features that aren't currently needed.

The current Sudoku puzzle is just a 9×9 grid. If and when zooming or other features become necessary, I'll add them then.

Test results

cb0951d 🟧 1B2 Image import and display adjustment
1bbdd52 🟢 When "paste" is executed, the clipboard contents are pasted onto the canvas.
3215af6 🟢 Nothing happens if the clipboard does not contain an image.
48e9e32 🟢 The image is fit to the canvas size (the image is automatically scaled up or down; the canvas itself is not scaled).
58ddef0 🟢 The image's aspect ratio is preserved.
faba506 (HEAD -> feat/paste-image) 🟢 The image is centered on the canvas.

Links

Source code, Live demo

Next step

🟧 1B3 Puzzle frame drawing

I'll draw a 9×9 grid over the pasted image.