Finish loading images before display changes?

51 Views Asked by At

I am using React JS. Upon submitting a form, I would like to load all of the 16x16px png images so that I can have and utilize their data (as an array containing 16 arrays, each with 16 values inside) BEFORE display is set to 'gameboard' and the next page gets rendered. The loading images part is in the convertAllLayouts function.

layoutShapes located in GameSettingsContext will be set as the loaded image data and, upon clicking the 'Start Game!' button, will then be used by the 'gameboard' display.

I tried having useEffect load the images, but it still loads the images after the 'gameboard' display component is rendered. Perhaps waiting to load the image has something to do with it?

Here is my code:

import { useState, useContext, useRef, useEffect } from "react";

import { GameSettingsContext, DisplayContext, AllCustomLayoutShapesContext } from "../../Game";

export default function CustomGameOptions() {
    const canvasRef = useRef('canvas#layout-canvas');

    const {display, setDisplay} = useContext(DisplayContext);

    const [isCustomLayoutShapesSubmitClicked, setIsCustomLayoutShapesSubmitClicked] = useState(false)

    const 
    {
        layoutShapes,
        setLayoutShapes,
    } = useContext(GameSettingsContext);

    const {
        presetLayouts, 
        setPresetLayouts, 
    } = useContext(AllCustomLayoutShapesContext);

    function convertAllLayouts(ref, presets) {
        let convertedPresets = [];
        let convertedLayouts = [];

        if (presetLayouts.length > 0) {

            for (let presetIndex = 0; presetIndex < presetLayouts.length; presetIndex++) {
                let newConvertedPreset = [];

                let img = new Image();
                img.crossOrigin='Anonymous';
                
                let ctx = canvasRef.current.getContext('2d', { alpha: true, desynchronized: false, colorSpace: 'srgb', willReadFrequently: true})
                ctx.globalCompositeOperation = "copy";

                let imageData;

                img.onload = function () {

                    ctx.width = img.width;
                    ctx.height = img.height;
                    ctx.imageSmoothingEnabled = false;
                    
                    ctx.drawImage(img, 0, 0);
                    imageData = ctx.getImageData(0, 0, 16, 16, {colorSpace: 'srgb'}).data;
                    for (let rowIndex = 0; rowIndex < 16; rowIndex++) {

                        let newRow = [];
                        
                        for (let i = 0 + 64 * rowIndex; i < 64 + 64 * rowIndex; i += 4) {
                            // every 4th imageData element is 
                            // the alpha channel for each pixel

                            if (imageData[i + 3] === 255) {
                                newRow.push(1);
                            } else if (imageData[i + 3] === 0) {
                                newRow.push(0);
                            }

                        }
                        newConvertedPreset.push(newRow)
                    }
                    convertedPresets.push(newConvertedPreset)
                }
                img.src = presetLayouts[presetIndex]
            }
        }

        convertedLayouts = [...convertedPresets]
        return convertedLayouts;
    }


    function handleClick(event) {
            setIsCustomLayoutShapesSubmitClicked(true)
    }

    useEffect(() => {
        if (isCustomLayoutShapesSubmitClicked === true) {
            const layouts = convertAllLayouts(canvasRef, presetLayouts)

            if (layouts.length === 0) {

                let defaultLayoutShape = []

                for (let rowIndex = 0; rowIndex < tileDimensions.h; rowIndex++) {

                    const newRow = new Array(tileDimensions.w).fill(1);

                    if ((tileDimensions.h * tileDimensions.w) % 2 !== 0) {
                        if (rowIndex === Math.floor(tileDimensions.h / 2)) {
                            newRow[Math.floor(tileDimensions.w / 2)] = 0;
                        }
                    } 
                    defaultLayoutShape.push(newRow);

                }
                setLayoutShapes([defaultLayoutShape, 'default']);
            }

            setLayoutShapes([...layouts]);
            setIsCustomLayoutShapesSubmitClicked(false)
            setDisplay('gameboard');

        }
    }, [isCustomLayoutShapesSubmitClicked])

    return (
        <article>
            <canvas id="layout-canvas" ref={canvasRef} width="16" height="16"></canvas>

            <button className="start-game-button" onClick={handleClick}>Start Game!</button>
        </article>
    )
}

Apologies for the lengthy block of code, I tried to trim it up a bit.

If anyone might know of what could be causing this issue, please let me know.

Thanks!

1

There are 1 best solutions below

0
Felt_Notebook On

I thought that doing .onload would be enough to wait to get the image data in order to then do whatever with it, but it turns out Promises do a better job at ensuring that the image data is there when it is needed.

I followed what this answer said, and applied it to my own code:

const convertPresets = async function (ref, presetLayouts) {
        let convertedLayouts = [];

        if (presetLayouts.length > 0) {

            for (let presetIndex = 0; presetIndex < presetLayouts.length; presetIndex++) {

                let newConvertedPreset = await new Promise((resolve, reject) => {

                    const preset = [];

                    let img = new Image();
                    img.crossOrigin='Anonymous';
                    
                    let ctx = ref.current.getContext('2d', { alpha: true, desynchronized: false, colorSpace: 'srgb', willReadFrequently: true})
                    ctx.translate(16, 16)
                    ctx.rotate(Math.PI)
                    ctx.globalCompositeOperation = "copy";

                    let imageData;

                    img.onload = async function () {

                        ctx.width = img.width;
                        ctx.height = img.height;
                        ctx.imageSmoothingEnabled = false;
                        
                        ctx.drawImage(img, 0, 0);
                        imageData = ctx.getImageData(0, 0, 16, 16, {colorSpace: 'srgb'}).data;
                        for (let rowIndex = 0; rowIndex < 16; rowIndex++) {

                            let newRow = [];
                            
                            for (let i = 0 + 64 * rowIndex; i < 64 + 64 * rowIndex; i += 4) {
                                // every 4th imageData element is 
                                // the alpha channel for each pixel

                                if (imageData[i + 3] === 255) {
                                    newRow.push(1);
                                } else if (imageData[i + 3] === 0) {
                                    newRow.push(0);
                                }

                            }

                            preset.push(newRow)

                        }

                        resolve(preset)

                    }

                    img.src = presetLayouts[presetIndex];

                }).then(data => data)

                convertedLayouts.push(newConvertedPreset);

            }

        }

        return convertedLayouts;

    }

And when I needed to execute this function, I simply did this:

const convertedPresetLayouts = await convertPresets(
   canvasRef,
   presetLayouts
)
.then((data) => data);

And the data was ready to be used for the next steps!