-1

I am trying to implement Remote FrameBuffer Protocol using Java socket programming. I have a server side program that takes screenshot of the entire screen using robot and store it in BufferedImage .Then I converted it into a byte array and sending it to the client .

Objective : To display the entire screen of the server side machine in a Swing GUI of the client side.

Problem i am facing : i am able to send the image in bytes from server and receive it from the server by the client (client.java) and convert it into a jpg image (output.jpg) using ImageIO and put that image in a Swing frame.

But i am able to see the first image in the Swing and whenever the image gets updated ,the image in the Swing is not updating or refreshing .

What I want : I want the image to refresh and show updated image every time the server sends the image data .

client.java

package remoteclient;

import java.lang.*;
import javax.imageio.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.net.*;
import java.io.*;

public class client  {

   
    public static void main(String args[])throws Exception{  
        Socket s=new Socket("localhost",5900);  
        DataInputStream din=new DataInputStream(s.getInputStream());  
        DataOutputStream dout=new DataOutputStream(s.getOutputStream());  
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));  
      
        int width=0,height=0;
        try {
            width = din.readInt(); //getting width and height from server thru socket.
            height = din.readInt(); 
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        JFrame f = new JFrame("Client");
        JLabel label = new JLabel();
       

        f.setSize(width, height);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         f.setVisible(true);

        boolean continueLoop = true;
        

        while(continueLoop)
        {
            try {
                
                int len = din.readInt();
                byte[] imageInByte = new byte[len];
                            
                System.out.println(len);

                din.readFully(imageInByte);

                System.out.println(imageInByte);
                
                ByteArrayInputStream bis = new ByteArrayInputStream(imageInByte);
                BufferedImage bImage2 = ImageIO.read(bis);
             //   Image im1 = bImage2.getScaledInstance(width,height, Image.SCALE_SMOOTH);

                
                ImageIO.write(bImage2, "jpg", new File("output.jpg") );
                bImage2 = ImageIO.read(new File("output.jpg"));
                label.setIcon(new ImageIcon(im1));
              
                ImageIcon icon = new ImageIcon(bImage2);
                icon.getImage().flush();
                label.setIcon( icon );
                f.getContentPane().add(label, BorderLayout.CENTER);
                
                f.pack();

           
            
           
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

  
    }
    }  


What I want : I want the image to refresh and show updated image every time the server sends the image data .

  • 2
    First and foremost, I'd get most all of that code out of a static main method (or static method of any type) and into the instance world. Next, I'd create a SwingWorker object to handle incoming data and either use the worker's publish/process method pair to update the image when received or attach a PropertyChangeListener to the worker so that my client GUI is notified when the image has been updated and sent. A key is to do all reading of incoming in a background thread, here the `doInBackground(...)` method of the SwingWorker, and avoid making any Swing calls from the same method. – Hovercraft Full Of Eels Nov 28 '22 at 20:54
  • As a conceptual [example](https://stackoverflow.com/questions/18505406/java-tcp-can-only-retrieve-image-once/18509880#18509880) - this manually grabs screen shots, but it wouldn't be hard to do it automatically – MadProgrammer Nov 28 '22 at 21:08

1 Answers1

1

Updated code with comments about demo code that should be removed from your working code:

Here's an example, using default UIManager icons, and SwingWorker, as noted in the comments to the original posting. You would instead use images from your server connection.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.UIManager;

public class SwingLabelWithUpdatedImage {

    public static void main(String args[]) throws Exception {

        final JLabel label = new JLabel("", SwingConstants.CENTER);

        final JFrame frame = new JFrame("Client");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(label, BorderLayout.CENTER);
        final Dimension preferredSize = new Dimension(200, 100);
        frame.setPreferredSize(preferredSize);
        frame.setVisible(true);
        frame.pack();

        final ImageUpdateWorker task = new ImageUpdateWorker(label);
        task.execute();
    }

    public static class ImageUpdateWorker extends SwingWorker<Void, IconInfo> {
        // iconInfoList is not need in your code. It's here so I can
        // supply a dummy set of icons to demonstrate UI updates.
        final List<IconInfo> iconInfoList;
        private JLabel label;

        ImageUpdateWorker(JLabel label) {
            this.label = label;
            // Delete this in your code
            this.iconInfoList = initIconInfoList();
        }

        @Override
        public Void doInBackground() {
            boolean isTrue = true;
            while (isTrue) {
                // Put your socket code to read the next icon from a server.
                // You don't need to do the ImageIO.write(), ImageIO.read() dance,
                // unless you must save the icon to disk. In that case, you don't need
                // to read it back in.

                // Here, I just rotate the iconInfoList to make it
                // appear as though a new icon was received.
                // Your code will not have any need to do this.
                Collections.rotate(iconInfoList, -1);
                // Just publish the icon you create from the image
                // you receive from your remote server.
                publish(iconInfoList.get(0));
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }

        @Override
        protected void process(List<IconInfo> icons) {
            // You might check for an empty list.
            // @kleopatra's suggestion to get the last icon is correct.
            // See https://docs.oracle.com/javase/tutorial/uiswing/concurrency/interim.html
            IconInfo iconInfo = icons.get(icons.size() - 1);
            label.setIcon(iconInfo.icon);
            // Your code will not do this 
            label.setText(iconInfo.name);
            // You can get the icon dimensions just from the icon, 
            // so you don't really need the IconInfo class.
            label.setSize(iconInfo.dimension);
        }

        /** Demo code only. It doesn't belong in your working code.
         */
        protected List<IconInfo> initIconInfoList() {
            // Just a quick way to get some icons; don't need to
            // fetch from a server just to demonstrate how to
            // refresh the UI.
            List<IconInfo> iconInfoList = UIManager.getDefaults().keySet().stream()
                .filter(this::isIconKey)
                .map(IconInfo::new)
                .filter(iconInfo -> iconInfo.icon != null)
                .collect(Collectors.toList());

            return iconInfoList;
        }

        /** Demo code only. It doesn't belong in your working code.
         */
        protected boolean isIconKey(Object key) {
            return String.class.isAssignableFrom(key.getClass())
                && ((String) key).toLowerCase().contains("icon");
        }
    }

    /** This is just a convenience to convey 
     * the icon and its UIManager key (i.e., name). 
     * Your remote server doesn't supply a name, 
     * so you don't really need this class.
     * It's just to make the demo more expressive.
     */
    public static class IconInfo {
        final private String name;
        final private Icon icon;
        final private Dimension dimension;

        IconInfo(Object name) {
            this.name = name.toString();
            icon = UIManager.getIcon(name);
            dimension = icon == null
                ? new Dimension(32, 32)
                : new Dimension(icon.getIconWidth(), icon.getIconHeight());
        }
    }

}
pfurbacher
  • 1,789
  • 3
  • 15
  • 23
  • nit-pick: I would take the last (instead of the first) of the chunks you get in process :) – kleopatra Nov 29 '22 at 11:28
  • Sure, or distance = -1. But really?! – pfurbacher Nov 30 '22 at 00:59
  • hmm .. don't understand what you mean, which distance? – kleopatra Nov 30 '22 at 03:38
  • ```Collections.rotate(list, distance)```. When set to -1 and list.get(0) you get the same result as your nit-pick about getting the last item with ```Collections.rotate(list, 1)```. – pfurbacher Nov 30 '22 at 03:49
  • that's not the point: the doc clearly states that there is _no_ 1:1 relationship between publish and process - multiple calls to publish can be coalesced into a single call of process (that's why the parameter of the latter is a list, to contain the values of all calls to the former). To be certain to use the latest we have to use the last element of that list given as parameter. Here its size most probably is 1 (given the füll second of sleeping) - so not a problem but a nit to be aware of – kleopatra Nov 30 '22 at 04:03
  • @pfurbacher in your comments - // Put your code to read the next icon from a server. are u referring to put the icon from server into the iconInfoList[0] ?.And where do you expect me to write the socket connection part ? – Mohammed Thabrez Nov 30 '22 at 15:23
  • @pfurbacher I added the socket connection and got the data but this time it is refreshing/reloading but I am only getting the name of the image and not the image itself .[GitHub link pls check it ](https://github.com/mdthabrez/RemoteFrameBufferProtocol/tree/main/remoteframebufferprotocol/src/remoteclient) can you check and tell it also has the server side code in src/remoteserver/ServerInitiator and it supporting file src/remoteserver/ScreenSpyer – Mohammed Thabrez Nov 30 '22 at 18:34
  • @kleopatra Got it. Thanks for pointing that out. Updating code accordingly. – pfurbacher Nov 30 '22 at 20:14
  • @MohammedThabrez First, remove the code from my example that was used to generate a quick and dirt list of icons. Remove ```protected boolean isIconKey(Object key)``` and ```protected List initIconInfoList()```. Get rid of ```final List iconInfoList;``` and its initialization. You don't use ```int wid,hei;```. Remove it. You don't really need the ```IconInfo```. Get rid of ```Collections.rotate(iconInfoList, -1);``` -- it makes no sense in your code. I used it as a way to supply an icon. You have your remote server. ... (continued) ... – pfurbacher Nov 30 '22 at 20:35
  • @MohammedThabrez Please see the updated code. Note that I also updated SwingWorker type declarations (```SwingWorker```) and the return type from ```doInBackground()``` accordingly. – pfurbacher Nov 30 '22 at 20:54