0

I am currently wring a card game program. The main class have two different JPanel inner classes. There are at most 10 Field JPanels and 18 Hand JPanels instances occuring at the same time, and each of them has a mouselistener with overriden codes in MousePressed. HOWEVER, every time I click on my card to perform an action, the program has about 2-3 seconds delay before it responds. I used YourKit to analyze the running time and it tells me that the sun.awt.image.ToolkitImage.getWidth(ImageObserver) is taking the longest reponse time (about 3 seconds). I am now really confused why getWidth(null) is so time-consuming. Attached is my paintComponent method and the result from YourKit. YourKit Result

@Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.WHITE);

        Image cardImage = CARD.getCurrentImage().getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH);

        int x = (getWidth() - cardImage.getWidth(null)) / 2;
        int y = (getHeight() - cardImage.getHeight(null)) / 2;

        g2d.setClip(new RoundRectangle2D.Double(
                0, 0, getWidth(), getHeight(), getWidth() / 4, getHeight() / 4));
        g2d.drawImage(cardImage, x, y, null);

        switch (CARD.getType()){
            case Card.FOLLOWER:
                Follower follower = (Follower) CARD;
                g2d.drawImage(ATK, 15,  getHeight() - 60, null);
                g2d.drawImage(DEF, getWidth() - 55, getHeight() - 60, null);

                AttributedString attack = new AttributedString(Integer.toString(follower.getAttack()));
                AttributedString health = new AttributedString(Integer.toString(follower.getHealth()));
                attack.addAttribute(TextAttribute.SIZE, 15);
                health.addAttribute(TextAttribute.SIZE, 15);
                attack.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
                health.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);

                g2d.drawString(attack.getIterator(), 35, getHeight() - 27);
                g2d.drawString(health.getIterator(), getWidth() - 35, getHeight() - 27);

                if (follower.hasWard()){
                    g2d.drawImage(WARD, (getWidth() - WARD.getWidth(null)) / 2,
                            (getHeight() - WARD.getHeight(null)) / 2, this);
                }

                if (follower.hasAmbush()){
                    g2d.drawImage(AMBUSH, (getWidth() - AMBUSH.getWidth(null)) / 2,
                            (getHeight() - AMBUSH.getHeight(null)) / 4, this);
                }

                if (follower.hasBane()) {
                    g2d.drawImage(BANE, 10, 10, this);
                }

                if (follower.hasDrain()){
                    g2d.drawImage(DRAIN, 50, 10, this);
                }

                switch (follower.getAtkStatus()){
                    case Follower.STORM_STAT:
                        g2d.setColor(Color.GREEN);
                        g2d.fillOval(getWidth() - 40, 8, 30,30);
                        g2d.setColor(Color.WHITE);
                        break;

                    case Follower.RUSH_STAT:
                        g2d.setColor(Color.YELLOW);
                        g2d.fillOval(getWidth() - 40, 8, 30, 30);
                        g2d.setColor(Color.WHITE);
                        break;

                    default:
                        g2d.setColor(Color.darkGray);
                        g2d.fillOval(getWidth() - 40, 8, 30, 30);
                        g2d.setColor(Color.WHITE);
                        break;
                }

                break;

            case Card.AMULET:
                if (((Amulet) CARD).isCountDown()){
                    g2d.drawString(Integer.toString(((Amulet) CARD).getCountDown()), getWidth() - 50, getHeight() - 50);
                }
                break;
        }
    }
SnowSR
  • 1
  • 2
    The first, most likely issue is: `Image cardImage = CARD.getCurrentImage().getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH);` - Scaling is computationally expensive. You should pre-scale the image and cache the result as you need it, you should never do this inside a `paint` method, as paint should run as fast as possible – MadProgrammer Nov 13 '17 at 19:41
  • 2
    `g2d.drawImage(ATK, 15, getHeight() - 60, null);` may or may not be an issue, but, you should, as a matter of habit, pass `this` as the `ImageObserver` reference to `drawImage`, as this allows the component to schedule paint requests itself should the image state change (i.e. loading) – MadProgrammer Nov 13 '17 at 19:42
  • 2
    Also be careful when change the clip or transformation of the `Graphics` context, where possible use a copy of the `Graphics` context by calling `Graphics#create`, this allows you to change the copies properties without affecting the the original, which could otherwise cause significant issues later – MadProgrammer Nov 13 '17 at 19:44
  • Right now I am having a ImageLib class fetching all image instances from files, so is that where I should be scaling my images? – SnowSR Nov 13 '17 at 19:45
  • 2
    It depends on what you're scaling to - if you're scaling to a fixed size, then yes, if you're scaling to a dynamic size, you need to perform it every time the size changes, but you want to do it only once per size change. It's hard to know, but since `Image` can be loaded in the background, the `ImageObserver` parameters allow observers to be notified when the image load state has changed, and in the case of something like `JComponent` schedule repaints automatically, so passing `null` could be forcing the component to "wait" or miss out on possible state changes. – MadProgrammer Nov 13 '17 at 19:47
  • 2
    Personally, I avoid `Image` for these reasons and rely on `BufferedImage`, because I know I don't need to worry about using `ImageObserver` as the image will be fully realised (especially if you're using `ImageIO.read` to load them) – MadProgrammer Nov 13 '17 at 19:48
  • Thank you very much for the help. I will modify my code and see if it helps with the lag xD – SnowSR Nov 13 '17 at 19:50
  • @MadProgrammer But how do i scale image using bufferedimage? The method returns image instead of bufferedimage – SnowSR Nov 13 '17 at 19:59
  • 1
    [For example](https://stackoverflow.com/questions/14115950/quality-of-image-after-resize-very-low-java/14116752#14116752), [example](https://stackoverflow.com/questions/11959758/java-maintaining-aspect-ratio-of-jpanel-background-image/11959928#11959928) and [an example of using `ComponentListener` to monitor `componentResized` events to resize images](https://stackoverflow.com/questions/24318051/width-and-height-of-a-jpanel-are-0-specific-situation/24318121#24318121) – MadProgrammer Nov 13 '17 at 20:20
  • @MadProgrammer Thank you very much! I solved this problem by resizing original image file thus eliminating getScaledInstance() from paintComponenet(). Now the response time is within 200ms – SnowSR Nov 14 '17 at 03:51

0 Answers0