SquirrelJME/SquirrelJME

View on GitHub
emulators/springcoat-vm/src/main/java/cc/squirreljme/vm/springcoat/MLEPencil.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package cc.squirreljme.vm.springcoat;

import cc.squirreljme.jvm.mle.PencilShelf;
import cc.squirreljme.jvm.mle.brackets.PencilBracket;
import cc.squirreljme.jvm.mle.constants.NativeImageLoadType;
import cc.squirreljme.jvm.mle.constants.PencilCapabilities;
import cc.squirreljme.jvm.mle.constants.PencilShelfError;
import cc.squirreljme.jvm.mle.constants.UIPixelFormat;
import cc.squirreljme.jvm.mle.exceptions.MLECallError;
import cc.squirreljme.runtime.cldc.debug.Debugging;
import cc.squirreljme.runtime.lcdui.mle.SoftwareGraphicsFactory;
import cc.squirreljme.vm.springcoat.brackets.PencilObject;
import cc.squirreljme.vm.springcoat.exceptions.SpringMLECallError;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.microedition.lcdui.AnimatedImage;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.ScalableImage;

/**
 * Handling for {@link PencilShelf}.
 *
 * @since 2020/09/26
 */
public enum MLEPencil
    implements MLEFunction
{
    /** {@link PencilShelf#capabilities(int)}. */
    CAPABILITIES("capabilities:(I)I")
    {
        /**
         * {@inheritDoc}
         * @since 2020/09/26
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            int pf = (int)__args[0];
            if (pf < 0 || pf >= UIPixelFormat.NUM_PIXEL_FORMATS)
                throw new SpringMLECallError("Invalid pixel format: " +
                    pf);
            
            return PencilCapabilities.MINIMUM |
                PencilCapabilities.COPY_AREA |
                PencilCapabilities.DRAW_LINE |
                PencilCapabilities.DRAW_RECT |
                PencilCapabilities.DRAW_XRGB32_REGION |
                PencilCapabilities.FILL_RECT |
                PencilCapabilities.FILL_TRIANGLE |
                PencilCapabilities.TEXT_BASIC;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareCopyArea(PencilBracket, int, int, int, int,
     * int, int, int)}.
     */
    HARDWARE_COPY_AREA("hardwareCopyArea:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;IIIIIII)V")
    {
        /**
         * {@inheritDoc}
         * @since 2023/02/19
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            try
            {
                MLEPencil.__graphics(__args[0])
                    .copyArea((Integer)__args[1],
                        (Integer)__args[2],
                        (Integer)__args[3],
                        (Integer)__args[4],
                        (Integer)__args[5],
                        (Integer)__args[6],
                        (Integer)__args[7]);
            }
            catch (IllegalArgumentException e)
            {
                throw new SpringMLECallError(e,
                    PencilShelfError.ILLEGAL_ARGUMENT);
            }
            catch (IllegalStateException e)
            {
                throw new SpringMLECallError(e, 
                    PencilShelfError.ILLEGAL_STATE);
            }
            
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareDrawChars(PencilBracket, char[], int, int,
     * int, int, int)}.
     */
    HARDWARE_DRAW_CHARS("hardwareDrawChars:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;[CIIIII)V")
    {
        /**
         * {@inheritDoc}
         * @since 2023/02/19
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            try
            {
                MLEPencil.__graphics(__args[0])
                    .drawChars(((SpringArrayObjectChar)__args[1]).array(),
                        (Integer)__args[2],
                        (Integer)__args[3],
                        (Integer)__args[4],
                        (Integer)__args[5],
                        (Integer)__args[6]);
            }
            catch (IllegalArgumentException e)
            {
                throw new SpringMLECallError(e,
                    PencilShelfError.ILLEGAL_ARGUMENT);
            }
            catch (IndexOutOfBoundsException e)
            {
                throw new SpringMLECallError(e,
                    PencilShelfError.INDEX_OUT_OF_BOUNDS);
            }
            
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareDrawLine(PencilBracket, int, int, int, int)}.
     */
    HARDWARE_DRAW_LINE("hardwareDrawLine:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;IIII)V")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .drawLine((Integer)__args[1],
                    (Integer)__args[2],
                    (Integer)__args[3],
                    (Integer)__args[4]);
            
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareDrawRect(PencilBracket, int, int, int, int)}.
     */
    HARDWARE_DRAW_RECT("hardwareDrawRect:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;IIII)V")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .drawRect((Integer)__args[1],
                    (Integer)__args[2],
                    (Integer)__args[3],
                    (Integer)__args[4]);
            
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareDrawSubstring(PencilBracket, String, int,
     * int, int, int, int)}.
     */
    HARDWARE_DRAW_SUBSTRING("hardwareDrawSubstring:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;" +
        "Ljava/lang/String;IIIII)V")
    {
        /**
         * {@inheritDoc}
         * @since 2023/02/19
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            try
            {
                MLEPencil.__graphics(__args[0])
                    .drawSubstring(
                        __thread.asNativeObject(String.class, __args[1]),
                        (Integer)__args[2],
                        (Integer)__args[3],
                        (Integer)__args[4],
                        (Integer)__args[5],
                        (Integer)__args[6]);
            }
            catch (IllegalArgumentException e)
            {
                throw new SpringMLECallError(e,
                    PencilShelfError.ILLEGAL_ARGUMENT);
            }
            catch (StringIndexOutOfBoundsException e)
            {
                throw new SpringMLECallError(e,
                    PencilShelfError.INDEX_OUT_OF_BOUNDS);
            }
            
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareDrawXRGB32Region(PencilBracket, int[], int,
     * int, boolean, int, int, int, int, int, int, int, int, int, int, int,
     * int)}.
     */
    HARDWARE_DRAW_XRGB32_REGION("hardwareDrawXRGB32Region:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;[IIIZIIIIIIIIIIII)V")
    {
        /**
         * {@inheritDoc}
         * @since 2021/01/26
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            // Try to get our buffer
            SpringArrayObjectInteger objBuf;
            
            try
            {
                objBuf = SpringNullObject.<SpringArrayObjectInteger>checkCast(
                        SpringArrayObjectInteger.class, __args[1]);
                if (objBuf == null)
                    throw new MLECallError("Null arguments.");
            }
            catch (ClassCastException e)
            {
                throw new MLECallError("Wrong buffer type.", e);
            }
            
            // Extract all the arguments
            int[] __buf = objBuf.array();
            int __off = (Integer)__args[2];
            int __scanLen = (Integer)__args[3];
            boolean __alpha = ((Integer)__args[4] != 0);
            int __xSrc = (Integer)__args[5];
            int __ySrc = (Integer)__args[6];
            int __wSrc = (Integer)__args[7];
            int __hSrc = (Integer)__args[8];
            int __trans = (Integer)__args[9];
            int __xDest = (Integer)__args[10];
            int __yDest = (Integer)__args[11];
            int __anch = (Integer)__args[12];
            int __wDest = (Integer)__args[13];
            int __hDest = (Integer)__args[14];
            int __origImgWidth = (Integer)__args[15];
            int __origImgHeight = (Integer)__args[16];
            
            // If the offset and/or the scan length is off, we need to correct
            // and move this over for the plain region call
            boolean booped = false;
            if (__off != 0 || __scanLen != __origImgWidth)
            {
                // Setup new buffer
                int maxSize = __origImgWidth * __origImgHeight;
                int[] newBuf = new int[maxSize];
                
                // Copy each scanline off
                int xSrc = __off;
                int xDst = 0;
                for (int y = 0; y < __origImgHeight; y++)
                {
                    // Copy row
                    System.arraycopy(__buf, xSrc,
                        newBuf, xDst, __origImgWidth);
                    
                    // Go to the next scan
                    xSrc += __scanLen;
                    xDst += __origImgWidth;
                }
                
                // We un-quirked the image, so reset these
                __buf = newBuf;
                __off = 0;
                __scanLen = __origImgWidth;
                booped = true;
            }
            
            // Load in source image
            Image wrapped = Image.createRGBImage(__buf,
                __origImgWidth, __origImgHeight, __alpha);
            
            // Forward to normal call and have our own graphics code handle
            // this one
            MLEPencil.__graphics(__args[0])
                .drawRegion(wrapped, __xSrc, __ySrc, __wSrc, __hSrc,
                    __trans, __xDest, __yDest, __anch, __wDest, __hDest);
            
            // No result from this one
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareFillRect(PencilBracket, int, int, int, int)}.
     */
    HARDWARE_FILL_RECT("hardwareFillRect:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;IIII)V")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .fillRect((Integer)__args[1],
                    (Integer)__args[2],
                    (Integer)__args[3],
                    (Integer)__args[4]);
            
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareFillTriangle(PencilBracket, int, int, int,
     * int, int, int)}.
     */
    HARDWARE_FILL_TRIANGLE("hardwareFillTriangle:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;IIIIII)V")
    {
        /**
         * {@inheritDoc}
         * @since 2023/02/16
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .fillTriangle((Integer)__args[1],
                    (Integer)__args[2],
                    (Integer)__args[3],
                    (Integer)__args[4],
                    (Integer)__args[5],
                    (Integer)__args[6]);
            
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareGraphics(int, int, int, Object, int, int[],
     * int, int, int, int)}.
     */ 
    HARDWARE_GRAPHICS("hardwareGraphics:(IIILjava/lang/Object;I[IIIII)" +
        "Lcc/squirreljme/jvm/mle/brackets/PencilBracket;")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            // Attempt to initialize the graphics
            try
            {
                int __pf = (Integer)__args[0];
                int __bw = (Integer)__args[1];
                int __bh = (Integer)__args[2];
                SpringArrayObject __buf =
                    SpringNullObject.<SpringArrayObject>checkCast(
                        SpringArrayObject.class, __args[3]);
                int __offset = (Integer)__args[4];
                SpringArrayObjectInteger __pal =
                    SpringNullObject.<SpringArrayObjectInteger>checkCast(
                        SpringArrayObjectInteger.class, __args[5]);
                int __sx = (Integer)__args[6];
                int __sy = (Integer)__args[7];
                int __sw = (Integer)__args[8];
                int __sh = (Integer)__args[9];
                
                return new PencilObject(__thread.machine,
                    SoftwareGraphicsFactory.softwareGraphics(
                        __pf, __bw, __bh,
                        (__buf != null ? __buf.array() : null),
                        __offset, (__pal != null ? __pal.array() : null),
                        __sx, __sy, __sw, __sh));
            }
            catch (ClassCastException|IllegalArgumentException|
                NullPointerException __e)
            {
                throw new SpringMLECallError(__e);
            }
        }
    },
    
    /** {@link PencilShelf#hardwareSetAlphaColor(PencilBracket, int)}. */
    HARDWARE_SET_ALPHA_COLOR("hardwareSetAlphaColor:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;I)V")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .setAlphaColor((Integer)__args[1]);
            
            return null;
        }
    }, 
    
    /** {@link PencilShelf#hardwareSetBlendingMode(PencilBracket, int)}. */
    HARDWARE_SET_BLENDING_MODE("hardwareSetBlendingMode:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;I)V")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .setBlendingMode((Integer)__args[1]);
            
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareSetClip(PencilBracket, int, int, int, int)}.
     */
    HARDWARE_SET_CLIP("hardwareSetClip:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;IIII)V")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .setClip((Integer)__args[1],
                    (Integer)__args[2],
                    (Integer)__args[3],
                    (Integer)__args[4]);
            
            return null;
        }
    },
    
    /** {@link PencilShelf#hardwareSetDefaultFont(PencilBracket)}. */
    HARDWARE_SET_DEFAULT_FONT("hardwareSetDefaultFont:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;)V")
    {
        /**
         * {@inheritDoc}
         * @since 2023/02/19
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .setFont(null);
            
            return null;
        }
    },
    
    /**
     * {@link PencilShelf#hardwareSetFont(PencilBracket, String, int, int)}.
     */
    HARDWARE_SET_FONT("hardwareSetFont:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;" +
        "Ljava/lang/String;II)V")
        {
            /**
             * {@inheritDoc}
             * @since 2023/02/19
             */
            @Override
            public Object handle(SpringThreadWorker __thread, Object... __args)
            {
                MLEPencil.__graphics(__args[0])
                    .setFont(Font.getFont(
                        __thread.asNativeObject(String.class, __args[1]),
                        (Integer)__args[2],
                        (Integer)__args[3]));
                
                return null;
            }
        },
    
    /** {@link PencilShelf#hardwareSetStrokeStyle(PencilBracket, int)}. */
    HARDWARE_SET_STROKE_STYLE("hardwareSetStrokeStyle:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;I)V")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .setStrokeStyle((Integer)__args[1]);
            
            return null;
        }
    }, 
    
    /** {@link PencilShelf#hardwareTranslate(PencilBracket, int, int)}. */
    HARDWARE_TRANSLATE("hardwareTranslate:" +
        "(Lcc/squirreljme/jvm/mle/brackets/PencilBracket;II)V")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            MLEPencil.__graphics(__args[0])
                .translate((Integer)__args[1],
                    (Integer)__args[2]);
            
            return null;
        }
    }, 
    
    /** {@link PencilShelf#nativeImageLoadTypes()}. */
    NATIVE_IMAGE_LOAD_TYPES("nativeImageLoadTypes:()I")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            // Say we support all, because we just use our own dispatcher!
            return NativeImageLoadType.ALL_TYPES;
        }
    },
    
    /**
     * {@link PencilShelf#nativeImageLoadRGBA(int, byte[], int, int,
     * cc.squirreljme.jvm.mle.callbacks.NativeImageLoadCallback)}.
     */
    NATIVE_IMAGE_LOAD_RGBA("nativeImageLoadRGBA:(I[BII" +
        "Lcc/squirreljme/jvm/mle/callbacks/NativeImageLoadCallback;)" +
        "Ljava/lang/Object;")
    {
        /**
         * {@inheritDoc}
         * @since 2021/12/05
         */
        @Override
        public Object handle(SpringThreadWorker __thread, Object... __args)
        {
            // Load parameters
            int type = (Integer)__args[0];
            SpringArrayObjectByte buf = (SpringArrayObjectByte)__args[1];
            int off = (Integer)__args[2];
            int len = (Integer)__args[3];
            SpringObject callbackRaw = (SpringObject)__args[4];
            
            if (buf == null || callbackRaw == null)
                throw new SpringMLECallError("No buf or callback.");
            if (off < 0 || len < 0 || (off + len) > buf.length())
                throw new SpringMLECallError("Out of bounds data.");
            
            // Callback for image loading
            NativeImageLoadCallbackAdapter callback =
                new NativeImageLoadCallbackAdapter(__thread.machine,
                    callbackRaw);
            
            // Does the native MLE handler support this?
            if ((PencilShelf.nativeImageLoadTypes() & type) != 0)
            {
                // Forward to MLE handler
                Object result = PencilShelf.nativeImageLoadRGBA(type,
                    buf.array(), off, len, callback); 
                    
                // Was this cancelled?
                if (result == null || result == SpringNullObject.NULL)
                    return SpringNullObject.NULL;
            }
            
            // Read from our image
            try (InputStream in = new ByteArrayInputStream(
                buf.array(), off, len))
            {
                // Load the image using our more native code, this will
                // forward into our native handler which means that we will
                // essentially get hyper accelerated images potentially
                Image image = Image.createImage(in);
                
                // Send initial image parameters
                callback.initialize(image.getWidth(),
                    image.getHeight(),
                    image.isAnimated(),
                    image.isScalable());
                
                // Scalable image
                if (image instanceof ScalableImage)
                    throw Debugging.todo();
                
                // Processing depends on the type of image this is
                else if (image instanceof AnimatedImage)
                {
                    // Set up the loop count of the image
                    AnimatedImage animated = (AnimatedImage)image;
                    callback.setLoopCount(animated.getLoopCount());
                    
                    for (int i = 0; i >= 0; i++)
                        try
                        {
                            MLEPencil.__addImage(callback,
                                animated.getFrame(i),
                                animated.getFrameDelay(i));
                        }
                        catch (IndexOutOfBoundsException ignored)
                        {
                            break;
                        }
                }
                
                // Still image
                else
                    MLEPencil.__addImage(callback, image, -1);
                
                // Check if it was cancelled
                Object finished = callback.finish();
                if (finished == null || finished == SpringNullObject.NULL)
                    return SpringNullObject.NULL;
                
                // Should finish as a SpringObject
                if (!(finished instanceof SpringObject))
                    throw new SpringMLECallError("Not an object?");
                return finished;
            }
            catch (IndexOutOfBoundsException|IOException|
                NullPointerException e)
            {
                throw new SpringMLECallError(e);
            }
        }
    }, 
    
    /* End. */
    ;
    
    /** The dispatch key. */
    protected final String key;
    
    /**
     * Initializes the dispatcher info.
     *
     * @param __key The key.
     * @throws NullPointerException On null arguments.
     * @since 2020/09/26
     */
    MLEPencil(String __key)
        throws NullPointerException
    {
        if (__key == null)
            throw new NullPointerException("NARG");
        
        this.key = __key;
    }
    
    /**
     * {@inheritDoc}
     * @since 2020/09/26
     */
    @Override
    public String key()
    {
        return this.key;
    }
    
    /**
     * Adds an image to be passed to the image loading callback.
     * 
     * @param __callback The callback to send to.
     * @param __frame The frame.
     * @param __frameDelay The frame delay.
     * @since 2022/06/28
     */
    static void __addImage(NativeImageLoadCallbackAdapter __callback,
        Image __frame, int __frameDelay)
        throws NullPointerException
    {
        if (__callback == null || __frame == null)
            throw new NullPointerException("NARG");
        
        // Try to get a direct RGB buffer from the image, this is faster
        // than doing an RGB copy of it
        int[] buf;
        int off, len;
        if (__frame.squirreljmeIsDirect())
        {
            buf = __frame.squirreljmeDirectRGBInt();
            off = __frame.squirreljmeDirectOffset();
            len = __frame.squirreljmeDirectScanLen() * __frame.getHeight();
        }
        
        // Otherwise, we need to load the RGB data from the image
        else
        {
            buf = new int[__frame.getWidth() * __frame.getHeight()];
            off = 0;
            len = buf.length;
            
            // Read in all the data
            __frame.getRGB(buf, 0, __frame.getWidth(),
                0, 0, __frame.getWidth(), __frame.getHeight());
        }
        
        // Send to the callback
        __callback.addImage(buf, off, len, __frameDelay, __frame.hasAlpha());
    }
    
    /**
     * Ensures that this is a {@link PencilObject} and returns the graphics
     * object for it.
     * 
     * @param __object The object to check.
     * @return As a {@link PencilObject}.
     * @throws SpringMLECallError If this is not one.
     * @since 2021/12/05
     */
    static Graphics __graphics(Object __object)
        throws SpringMLECallError
    {
        if (!(__object instanceof PencilObject))
            throw new SpringMLECallError("Not a PencilObject.");
        
        return ((PencilObject)__object).graphics; 
    }
}