Chào mừng!

Bằng cách đăng ký với chúng tôi, bạn sẽ có thể thảo luận, chia sẻ và nhắn tin riêng tư với các thành viên khác trong cộng đồng của chúng tôi.

Đăng ký ngay!
  • Chào Khách,
    Bạn cần liên hệ với admin ??? ZALO & TELEGRAM

Hướng Dẫn Webcam in JAVA

Tham gia
15/1/24
Bài viết
10
Lượt Thích
5
Coins
3,790
Hi everyone,

I believe everyone knows the animated image called GIF (or Graphics Interchange Format). As an IT developer, have you ever thought about how you can develop an app that captures videos and converts the images into a GIF file? This is very easy in JAVA. I'll show you how to create an app that captures videos (e.g. YouTube or any animated GIF) on the screen and lets you create your own GIF files that can be edited, etc.

First of all, an animation is a sequence of images that are slightly different from each other and occur in a rapid timeframe. Normally the animation is vivid when the sequence of timeframes is around 25 frames per second. There are two ways to capture images on the screen: an external camera or a screenshot. The first option is out of the question. The second option basically simulates an external camera that can take the screenshots fast enough to display a vivid animation (i.e. 25 time frames per second).

Then some words about coding: Coding is a question of belief. There is someone who relies only on framework and IDE which produce a heap of small APIs grouped into "package". The others, for example, love simple but convenient editors like Notepad++ or Jedit. Whatever the people like, coding is similar to building of a house: first the foundation, thenthe house frame, then the roof, then the door and windows, etc. None of the works can be done in parallel. For this reason, I will show you the codes and basic development of an application without going into frameworks or IDEs.

The basic development environment:
1) Working directory: for example: C:\myapp\gif (similar in Linux), where the sources are created.
2) Compile: Open a CMD window (terminal under Linux).
CMD window
3) Set CLASSPATH. For example, on WINDOWS: Create a batch file setCP.bat as follows:
Mã:
set CLASSPATH=%CLASSPATH%;.;.\classes
The 3rd step includes the current (a dot) working directory and the subdirectory “classes” (.\classes) to the Java variable CLASSPATH (Similar to a shell file for Linux).

The source that simulates a camera is called CamCorder.java (Cam for Camera and Coder by reCorder). As mentioned above, we lay the foundation for CamCorder with the control panel, then the camera frame is the “lens”. From them we begin to develop the functionalities of a virtual camera. The source is written in JAVA JFX. At the end of this tutorial, I'll post the complete package of CamCorder here in ZIP format.

The CamCorder Control Panel (CamCorder.java)
Java:
public class CamCorder extends Application {
  //
  public void start(Stage panel) {
    this.panel = panel;
    panel.setOnCloseRequest(ev -> {
      ev.consume(); // Ignore the close by pressing X in the rightmost upper corner
    });
    ...
    focus = Tools.getButton("FOCUS", "Focus Screen Image");
    focus.setOnAction(ev -> {
    });
    start = Tools.getButton("START", "Start Recording Screen");
    start.setOnAction(ev -> {
      if (!focused) {
        System.out.println("Nowhere on SCREEN is focused.");
        return;
      }
      if (idle) {
        start.setText("STOP");
      } else {
        start.setText("START");
      }
    });
    //
    load = Tools.getButton("LOAD", "Load from GIF/GZIP image file");
    load.setOnAction(ev -> {
    });
    edit = Tools.getButton("EDIT", "Edit BufferedImages");
    edit.setOnAction(ev -> {
      editing();
    });
    undo = Tools.getButton("UNDO", "Undo Editing");
    undo.setOnAction(ev -> {
    });
    show = Tools.getButton("SHOW", "Show animated BufferedImages");
    show.setOnAction(ev -> {
    });
    save = Tools.getButton(new Image(CamCorder.class.getResourceAsStream("save.png")),
                                 "Save to GIF/GZIP file");
    save.setOnAction(ev -> {
    });
    reset = Tools.getButton("RESET", "RESET");
    reset.setOnAction(ev -> {
    });
    Button quit = Tools.getButton("QUIT", "Quit & Exit CamCorder");
    quit.setOnAction(ev -> {
      panel.close();
      Platform.exit();
      System.exit(0);
    });
    VBox bBox = new VBox(5);
    bBox.setAlignment(Pos.CENTER);
    // Insets(top, right, bottom, left)
    bBox.setPadding(new Insets(10, 0, 15, 0));
    bBox.getChildren().addAll(focus, start, load, edit, undo, show, save, reset, quit);
    Scene scene = new Scene(bBox, 145, 350);
    scene.getStylesheets().add("gif.css");
    panel.getIcons().add(new Image(CamCorder.class.getResourceAsStream("sanduhr.gif")));
    panel.setAlwaysOnTop(true);
    panel.setResizable(false);
    panel.setScene(scene);
    panel.setY(0);
    panel.setX(0);
    panel.show();
The Tools codes (Tools.java):
Java:
public class Tools {
  ...
  /**
   @param icon JFX Image or String
   @param txt String as tooltip or button name (if icon = null)
   @return Button
  */
  public static Button getButton(Object icon, String txt) {
    Button button = null;
    if (icon == null || icon instanceof String) {
      button = new Button(icon == null? txt:(String)icon);
      // direct CSS style
      button.setStyle("-fx-font-size:11; -fx-font-weight: bold;");
    } else try {
      button = new Button( );
      button.setGraphic(new ImageView((Image) icon));
    } catch (Exception ex) {
      button = new Button(txt);
      button.setStyle("-fx-font-weight: bold;");
    }
    Tooltip tt = new Tooltip();
    tt.setText(txt);
    tt.setStyle("-fx-base: #AE3522; -fx-text-fill: orange; -fx-font-cb: bold;");
    button.setTooltip(tt);
    return button;
  }

CamCorder ControlPanel

Explanation:
- All JFX buttons are activated by lambda expression (e -> { ... });
- Line scene.getStylesheets().add("gif.css") uses the style from the external file gif.css
- Line panel.setAlwaysOnTop(true) always places the ControlPanel above other applications.
(to be continued)
 
Sửa lần cuối:
Tham gia
15/1/24
Bài viết
10
Lượt Thích
5
Coins
3,790
(Continuation)
In the previous section I showed you the ControlPanel, which is the foundation and contains 9 buttons. With the implementation of Tools.getButton(), a hint text with the description of the button appears when the mouse pointer hovers over the buttons. Nine buttons means 9 CamCorder activities need to be implemented. The foundation has been laid. Today I'll show you the CamCorder frame, which is the "lens" of the virtual camera. The lens is nothing more than a semi-transparent JFX Canvas that covers the entire screen and allows you to see the actual scene on the screen, allowing you to focus on the specific area using the FOCUS button in the ControlPanel.
Java:
public class CamCorder extends Application {
  //
  public void start(Stage panel) {
    ...
    // Create a lens for CamCorder
    try { // take a screen shot to get the screen size
      BufferedImage screen = (new java.awt.Robot()).
        createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
      wMx = screen.getWidth(); // max Window width
      hMx = screen.getHeight();// max Window height
    } catch (Exception ex) {
      wMx = 1366; hMx = 768;
    }
    // create canvas of screen size
    Canvas canvas = new Canvas(wMx, hMx);
    // create the 2nd JFX Stage for Canvas
    Stage stage = new Stage();
    Pane pane = new Pane();
    pane.getChildren().addAll(canvas);
    // hMx+13: adjust to the FrameBar with Iconify/Enlarge/Close button
    Scene window = new Scene(pane, wMx, hMx+13);
    window.setFill(Color.TRANSPARENT); // transparent window scene
    window.getStylesheets().add("gif.css");
    stage.setScene(window);
    // set semi-opacity (semi-transparent)
    stage.setOpacity(0.5);
    //
    flag = false;
    move = false;
    focused = false;
    gc = canvas.getGraphicsContext2D( );
    gc.setFontSmoothingType(FontSmoothingType.GRAY);
    canvas.addEventFilter(MouseEvent.ANY, (e) -> canvas.requestFocus());
    // Focussing with the mouse pointer
    canvas.setOnMouseMoved(e -> {
      if (!move || !flag) return;
      int xx = (int)e.getX();
      int yy = (int)e.getY();
      //drawing rectangle....
      gc.clearRect(0, 0, wMx, hMx);
      gc.setLineWidth(2);
      gc.setStroke(Color.RED);
      gc.strokeLine(x0, y0, xx, y0);
      gc.strokeLine(x0, yy, xx, yy);
      gc.strokeLine(x0, y0, x0, yy);
      gc.strokeLine(xx, y0, xx, yy);
    });
    canvas.setOnMouseReleased(e -> {
      if (!flag) return;
      int xx = (int)e.getX();
      int yy = (int)e.getY();
      // 1st Click: upper coordinate
      if (x0 < 0) {
        x0 = xx;
        y0 = yy;
        move = true;
        return;
      }
      // 2nd Click: lower coordinate
      move = false;
      flag = false;
      // setting focusing area: x0, y0, width and height
      if (x0 > xx) {
        int x = x0;
        x0 = xx;
        xx = x;
      }
      if (y0 > yy) {
        int y = y0;
        y0 = yy;
        yy = y;
      }
      // the size of focussing area
      width = xx-x0;
      height = yy-y0;
      // wait 200 ms before disappearing
      waiting(200);
      stage.hide();
      disable(false);
      focused = true;
    });
    ...
    // create virtual Camera
    ForkJoinPool.commonPool().execute(() -> {
      BufferedImage bImg, image;
      while (running) {
        while (idle)
          waiting(100);
        while (!done) {
          try { // take screenshot
            bImg = (new java.awt.Robot()).
                   createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
            image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            // get focussed image
            for (int y = y0, b = 0; y < hMx && b < height; ++y, ++b)
              for (int x = x0, a = 0; x < wMx && a < width; ++x, ++a)
                image.setRGB(a, b, bImg.getRGB(x,y));
            // save in Image List
            bImgList.add(image);
            TimeUnit.MILLISECONDS.sleep(20);
          } catch (Exception ex) { }
        }
      }
    });
    ...
The line ForkJoinPool.commonPool().execute(() -> { ... }); starts a background Snapshot-taking task in ForkJoinPool. It runs till running is set to false (Button QUIT). It stands idle as long as idle is true. More see START/STOP.
Now we can implement the buttons lambda expression:

FOCUS button: Allows you to focus on a specific area on the screen. In this Lambda Expression the canvas is cleared and all
Java:
    focus = Tools.getButton("FOCUS", "Focus Screen Image");
    focus.setOnAction(ev -> {
      gc.clearRect(0, 0, wMx, hMx);
      disable(true); // disable all buttons
      stage.show();  // show the canvas in semi-transparent
      x0 = y0 = -1;  // reset first coordinate XY
      flag = true;   // set Flag telling FOCUSING is ready
    });

Focussing

START/STOP button: This button switches the display between START (take pictures) and STOP (stop recording). The time between START (1st click) and STOP (2nd click) is the display interval of the captured images. The START/STOP action only works if FOCUSING has been performed beforehand.
Java:
    start = Tools.getButton("START", "Start Recording Screen");
    start.setOnAction(ev -> {
      if (!focused) {
        Tools.dialog(panel, true, "Nowhere on SCREEN is focused.");
        return;
      }
      if (idle) { // START
        disable(true);
        // except START/STOP
        start.setDisable(false);
        start.setText("STOP"); // change text to STOP
        bImgList.clear(); // clear Image List
        saveList.clear(); // clear Save List
        idle = false;     // start ScreenShot
        done = false;     //
      } else { // End of taking images
        start.setText("START");
        disable(false);
        idle = true;
        done = true;
      }
    });
    ...
LOAD button: Loads an existing GIF/GZIP file for editing or review.
Java:
    load = Tools.getButton("LOAD", "Load from GIF/GZIP image file");
    load.setOnAction(ev -> {
      disable(true);
      fName = Tools.getFile(panel, dir);
      if (fName != null) try {
        if (fName.toLowerCase().lastIndexOf(".gif") > 0) {
          bImgList = GzipWriter.extractGIF(fName);
        } else {
          bImgList = GifWriter.extractGZIP(fName);
        }
        focused = false;
      } catch (Exception ex) {
        Tools.dialog(panel, false, ex.toString());
      }
      disable(false);
    });
EDIT button: Allows you to edit the GIF/GZIP file.
UNDO button: Undoes the edited GIF/GZIP file.
Java:
    edit = Tools.getButton("EDIT", "Edit BufferedImages");
    edit.setOnAction(ev -> {
      editing();
    });
    undo = Tools.getButton("UNDO", "Undo Editing");
    undo.setOnAction(ev -> {
      if (!saveList.isEmpty()) {
        disable(true);
        bImgList.clear(); // remove the content
        for (BufferedImage img:saveList) bImgList.add(Tools.iClone(img));
        disable(false);
      }
      Tools.dialog(panel, true, "Original BufferedImage List is restored");
    });
    ...
  // this is an editing dialog
  private void editing() {
    int mx = bImgList.size();
    disable(true);
    if (mx > 0) try { // NOT empty
      BufferedImage img = bImgList.get(0);
      int x = img.getWidth();
      int y = img.getHeight();
      if (x < 100 || y < 100) {
        Tools.dialog(panel, false, "Images are too small for Editing. Min. 100x100.");
        disable(false);
        return;
      }
      if (saveList.isEmpty()) // clone the original
        for (BufferedImage bimg:bImgList) saveList.add(Tools.iClone(bimg));
      //
      Dialog<ButtonType> dia = new Dialog<>();
      dia.setTitle("CamCorder");
      dia.setHeaderText("Edit GIF/GZIP Images");
      DialogPane dp = dia.getDialogPane();
      dp.getStylesheets().add("gif.css");
      dp.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
      TextField tfa = new TextField("Any Text");
      tfa.setPrefWidth(245);
      HBox h1 = new HBox(5);
      h1.setAlignment(Pos.CENTER_LEFT);
      h1.getChildren().addAll(new Label("Your Text    "), tfa);
      //
      TextField tff = Tools.getTextField(0);
      TextField tfe = Tools.getTextField(mx);
      TextField tfo = Tools.getTextField(15);
      HBox h2 = new HBox(5);
      h2.setAlignment(Pos.CENTER_LEFT);
      h2.getChildren().addAll(new Label("From Image"), tff,
                              new Label("To Image"), tfe,
                              new Label("Font Size"), tfo);
      ComboBox<String> cbc = new ComboBox<>();
      cbc.getItems().addAll("YELLOW!WHITE!BLACK!GREEN!BLUE!RED".split("!"));
      java.awt.Color colors[] = { java.awt.Color.YELLOW,
                                  java.awt.Color.WHITE,
                                  java.awt.Color.BLACK,
                                  java.awt.Color.GREEN,
                                  java.awt.Color.BLUE,
                                  java.awt.Color.RED
                                };
      idx = 0;                   
      cbc.setValue("YELLOW");
      cbc.setOnAction(a -> {
        idx = cbc.getSelectionModel().getSelectedIndex();
      });
      ComboBox<String> cbf = new ComboBox<>();
      cbf.getItems().addAll("Arial!Courier!Georgia!Lucida!Times!Tahoma!Verdana".split("!"));
      cbf.setValue("Georgia");
      font = "Georgia";
      cbf.setOnAction(a -> {
        font = cbf.getSelectionModel().getSelectedItem();
        if ("Times".equals(font)) font = "Times New Roman";
      });
      type = 0;
      int types[] = { java.awt.Font.BOLD,
                      java.awt.Font.ITALIC,
                      java.awt.Font.PLAIN
                    };
      ComboBox<String> cbt = new ComboBox<>();
      cbt.getItems().addAll("BOLD!ITALIC!PLAIN".split("!"));
      cbt.setValue("BOLD");
      cbt.setOnAction(a -> {
        type = cbt.getSelectionModel().getSelectedIndex(); 
      });
      HBox h3 = new HBox(17);
      h3.setAlignment(Pos.CENTER_LEFT);
      h3.getChildren().addAll(cbc, cbf, cbt);
      //
      TextField tfx = Tools.getTextField(10);
      TextField tfy = Tools.getTextField(10);
      HBox h4 = new HBox(10);
      h4.setAlignment(Pos.CENTER_LEFT);
      h4.getChildren().addAll(new Label("X position (0.."+x+")"), tfx,
                              new Label("Y position (0.."+y+")"), tfy);
      VBox box = new VBox(5);
      box.getChildren().addAll(h1, h2, h4, h3);
      dp.setContent(box);
      dp.setPrefSize(400, 200);
      Tools.setPosition(panel, dia);
      if (dia.showAndWait().get() == ButtonType.CANCEL) {
        disable(false);
        return;
      }
      x = Integer.parseInt(tfx.getText().trim());
      y = Integer.parseInt(tfy.getText().trim());
      int b = Integer.parseInt(tff.getText().trim());
      int e = Integer.parseInt(tfe.getText().trim());
      if (e > mx) e = mx;
      if (b >= e) {
        Tools.dialog(panel, false, "Begin at:"+b+". End at:"+e+"?");
        return;
      }
      String text = tfa.getText().trim();
      int fsize = Integer.parseInt(tfo.getText().trim());
      if (fsize < 10) fsize = 10;
      if (fsize > 40) fsize = 40;
      if (y < fsize) y = fsize+5;
      for (int i = b; i < e; ++i) {
        java.awt.Graphics2D g =(java.awt.Graphics2D)(bImgList.get(i)).getGraphics();
        g.setFont(new java.awt.Font(font, types[type], fsize));
        g.setPaint(colors[idx]);
        g.drawString(text, x, y);
        g.dispose();
      }
    } catch (Exception ex) {
      Tools.dialog(panel, false, ex.toString());
    }
    disable(false);
  }
  // start, load, edit, save,...;
  private void disable(boolean boo) {
    focus.setDisable(boo);
    start.setDisable(boo);
    reset.setDisable(boo);
    show.setDisable(boo);
    load.setDisable(boo);
    edit.setDisable(boo);
    save.setDisable(boo);
    undo.setDisable(boo);
  }
  ...

Editing Dialog
(to be continued)
 
Sửa lần cuối:
Tham gia
15/1/24
Bài viết
10
Lượt Thích
5
Coins
3,790
(Continuation)

Today we start with the last 4 ControlPanel buttons. The codes are straightforward with comments. You may notice that some methods are called with the prefix "Tools/GzipWriter/GifWriter" or initiated as "new Display()". They are part of the CamCorder package, as well as the CSS file "gif.css". And I will explain them later in this tutorial.

SHOW button: Shows the captured images (with/without edited text).
Java:
    show = Tools.getButton("SHOW", "Show animated BufferedImages");
    show.setOnAction(ev -> {
      int mx = bImgList.size();
      if (mx == 0) return; // Nothing to show
      //
      Dialog<ButtonType> dia = new Dialog<>();
      dia.setTitle("CamCorder");
      dia.setHeaderText("SHOW GIF/GZIP Images");
      DialogPane dp = dia.getDialogPane();
      dp.getStylesheets().add("gif.css");
      dp.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
      //
      TextField tff = Tools.getTextField(0);
      TextField tfe = Tools.getTextField(mx);
      CheckBox cb = new CheckBox("Reverse");
      HBox hbox = new HBox(7);
      hbox.setAlignment(Pos.CENTER);
      hbox.getChildren().addAll(new Label("From Image"), tff,
                                new Label("To Image"), tfe, cb);
      dp.setContent(hbox);
      dp.setPrefSize(400, 100);
      Tools.setPosition(panel, dia);
      if (dia.showAndWait().get() == ButtonType.OK) {
        int b = Integer.parseInt(tff.getText());
        int e = Integer.parseInt(tfe.getText());
        if (b >= e) {
          Tools.dialog(panel, false, "Begin: "+b+" >= end: "+e);
          return;
        }
        ArrayList<BufferedImage> al = new ArrayList<>();
        if (cb.isSelected()) // forward or reverse ?
             for (int i = e-1; i >= b; --i) al.add(bImgList.get(i));
        else for (int i = b; i < e; ++i) al.add(bImgList.get(i));
        new Display(al, 40); // start display the captured/loaded image
      }
    });

The original:


and the edited/reduced giphy.gif (0.5 of the original)


SAVE button with a floppy disk icon: Saves the captured/edited images in a GIF/GZIP file.
RESET button: Resets the focussed area.
QUIT button: Quit and exit CamCorder. Before exit, virtual camera will be stopped and all stages will be closed.
Java:
    save = Tools.getButton(new Image(CamCorder.class.getResourceAsStream("save.png")),
                                 "Save to GIF/GZIP file");
    save.setOnAction(ev -> {
      saving();
    });
    reset = Tools.getButton("RESET", "RESET");
    reset.setOnAction(ev -> {
      idle = true;
      done = true;
      flag = false;
      move = false;
      x0 = y0 = -1;
      focused = false;
      saveList.clear();
      bImgList.clear();
    });
    Button quit = Tools.getButton("QUIT", "Quit & Exit CamCorder");
    quit.setOnAction(ev -> {
      done = true;
      idle = false;
      running = false; // stop Virtual Camera
      //
      waiting(10);
      // close CanvasPanel and ControlPanel
      stage,close();
      panel.close();
      Platform.exit();
      System.exit(0);
    });
    ...
  private void saving() {
    int mx = bImgList.size();
    if (mx == 0) return;
    //
    disable(true);
    // set the default name
    if (fName == null) fName = dir+File.separator+"image.gif";
    fName = Tools.getFile(panel, fName, "SAVE file as GIF/GZIP");
    if (fName == null) { // empty name
      disable(false);
      return;
    }
    Dialog<ButtonType> dia = new Dialog<>();
    dia.setTitle("CamCorder");
    dia.setHeaderText("SAVE GIF/GZIP Images");
    DialogPane dp = dia.getDialogPane();
    dp.getStylesheets().add("gif.css");
    dp.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
    //
    TextField tff = Tools.getTextField(0);
    TextField tfe = Tools.getTextField(mx);
    CheckBox cb = new CheckBox("Reverse");
    HBox hbox = new HBox(7);
    hbox.setAlignment(Pos.CENTER);
    hbox.getChildren().addAll(new Label("From Image"), tff,
                              new Label("To Image"), tfe, cb);
    //
    ratio = 1.0f; // default is 1:1
    Slider slider = Tools.getSlider(420, 40);
    slider.valueProperty().addListener((observable, oValue, nValue) -> {
      ratio =  (float)nValue.doubleValue(); // zooming IN/OUT ratio
    });
    VBox vbox = new VBox(5);
    vbox.getChildren().addAll(hbox, slider);
    dp.setContent(vbox);
    dp.setPrefSize(440, 150);
    Tools.setPosition(panel, dia);
    if (dia.showAndWait().get() == ButtonType.CANCEL) {
      disable(false);
      return;
    }
    int b = Integer.parseInt(tff.getText());
    int e = Integer.parseInt(tfe.getText());
    if (b >= e) {
      disable(false);
      Tools.dialog(panel, false, "Begin: "+b+" >= end: "+e);
      return;
    }
    ArrayList<BufferedImage> al = new ArrayList<>();
    if (cb.isSelected()) // forward or reverse ?
         for (int i = e-1; i >= b; --i) al.add(bImgList.get(i));
    else for (int i = b; i < e; ++i) al.add(bImgList.get(i));
    // create an animated file with GIF or GZIP format
    try {
      if (fName.toLowerCase().indexOf(".gif") < 0)
           GzipWriter.createGZIP(ratio, al, fName);
      else GifWriter.createGIF(100, ratio, al, fName, 0);
      // save directory as default
      int n = fName.lastIndexOf(File.separator);
      if (n > 0) dir = fName.substring(0, n);
    } catch (Exception ex) {
      Tools.dialog(panel, false, ex.toString());
    }
    disable(false);
  }
  //
  private void waiting(int time) {
    try {
      TimeUnit.MILLISECONDS.sleep(time);
    } catch (Exception ex) { }
  }
As you can see, the CamCorder app is “dialog intensive”. Each CamCorder action results in a self-explanatory dialog, while the other buttons (except QUIT) are disabled to prevent unwanted overlap of two or more CamCorder actions.
The SAVE dialog begins with a request for a file name “Working Directory\GIFimages\image.gif” for captured images. When the images are loaded, the filename is the loaded filename. If you empty the input field and press ENTER, a FileChooser will appear where you can select any name. Either with this name or you can overwrite the name with a different name for saving. After you click OK, a slider will appear to enlarge (more than 1) or to reduce (less than 1) the images (Zoom In/Out).



You might be wondering why I'm talking about GZIP together with GIF, right? Let me explain:
GIF was developed by a team at online service provider CompuServe led by American computer scientist Steve Wilhite and released on June 15, 1987, while GZIP is a file format and software application for file compression and decompression. The program was created by Jean-loup Gailly and Mark Adler as a free software replacement for the compression program used in early Unix systems, and was intended for use by GNU (from which the "g" of gzip is derived). Version 0.1 was first publicly released on October 31, 1992, version 1.0 followed in February 1993 (sources: WIKIPEDIA). GZIP compressed images are “smaller” than GIF images, which are compressed using the Lempel-Ziv-Welch (LZW) lossless data compression technique to reduce file size without compromising visual quality. The ratio between GIF:GZIP is approximately 8:1 and the differences between the GIF and GZIP images are so marginal that one could hardly notice them. For this purpose I developed the GzipWriter alongside the GifWriter. Both writers will be explained in the next sessions.

Note: The GZIP image file cannot be viewed/played by browsers or other imaging standard tools - except this CamCorder.
(to be continued)
 
Sửa lần cuối:
Tham gia
15/1/24
Bài viết
10
Lượt Thích
5
Coins
3,790
(continuation)

A few words about Linux (here: Ubuntu or Mint): JAVA JAR files should be registered in the global CLASSPATH in your .profile (dot profile) as follows:
Mã:
export CLASSPATH=$HOME/java/jdk-9.0.4/lib:.:./classes:$HOME/JoeApp/gif/CamCorder.jar
or as a shell script (for local use)
Mã:
export CLASSPATH=$CLASSPATH:.:./classes
java -jar CamCorder.jar
Again the dot (.) for the current working directory and ./classes for the subdirectory classes

Here is an animated piece of the VanCleef Cowboy movie as a GIF file on Linux Mint (see line: java -jar Camcorder.jar. The jar file is copied from Windows 10 to Linux Mint without modification.)


I have discussed with you in three previous sessions about the 9 activities of ControlPanel or the foundation of CamCorder. Today I will tell you about the frame or canvas that the virtual camera represents. As you learned in the 2nd session, the canvas has its own JFX Stage/Scene and is executed by a background task in ForkJoinPool. The ForkJoinPool is the extension of JAVA ExecutorService and is tailored for parallel “work stealing mode” (best for concurrency). The virtual camera is activated by FOCUS and then START and set to idle by STOP.
Java:
    Canvas canvas = new Canvas(wMx, hMx);
    saveList = new ArrayList<>();
    bImgList = new ArrayList<>();
    // Focusing window
    Stage stage = new Stage();
    Pane pane = new Pane();
    pane.getChildren().addAll(canvas);
    // hMx+13: adjust to the FrameBar with Iconify/Enlarge/Close button
    Scene window = new Scene(pane, wMx, hMx+13);
    window.setFill(Color.TRANSPARENT);
    window.getStylesheets().add("gif.css");
    stage.setScene(window);
    stage.setOpacity(0.5);
    // Get GraphicsContex and set Focus
    gc = canvas.getGraphicsContext2D( );
    gc.setFontSmoothingType(FontSmoothingType.GRAY);
    canvas.addEventFilter(MouseEvent.ANY, (e) -> canvas.requestFocus());
The line "window.setFill(Color.TRANSPARENT);" and "stage.setOpacity(0.5);" tell JVM that the Scene is 100% transparent, but the Stage should be "semi-transparent" (0.5). The effect is to cover the computer screen with a “haze” so you can continue to see what’s going on underneath. The following codes show you how to focus on a specific area on the computer screen:
1) Draw a red rectangle: After the first mouse click for the starting coordinate (x0/y0), a red rectangle is continuously drawn with the moving mouse pointer.
Java:
    canvas.setOnMouseMoved(e -> {
      if (!move || !flag) return;
      int xx = (int)e.getX();
      int yy = (int)e.getY();
      //clear the canvas....
      gc.clearRect(0, 0, wMx, hMx);
      // draw a red rectangle
      gc.setLineWidth(2);
      gc.setStroke(Color.RED);
      gc.strokeLine(x0, y0, xx, y0);
      gc.strokeLine(x0, yy, xx, yy);
      gc.strokeLine(x0, y0, x0, yy);
      gc.strokeLine(xx, y0, xx, yy);
    });
2) Focus area: With the second mouse click you set the 2nd coordinate of the focus area. The red focus frame (rectangle) disappears (stage.hide() -see Picture in Session 2, FOCUS button) and you can now start and stop taking the screenshot using the START/STOP button.
Java:
    canvas.setOnMouseReleased(e -> {
      if (!flag) return;
      int xx = (int)e.getX();
      int yy = (int)e.getY();
      // Starting coordinate?
      if (x0 < 0) {
        x0 = xx;
        y0 = yy;
        move = true;
        return;
      }
      // second coordinate
      move = false;
      flag = false;
      // setting focus area:
      // x0, y0, width and height
      if (x0 > xx) {
        int x = x0;
        x0 = xx;
        xx = x;
      }
      if (y0 > yy) {
        int y = y0;
        y0 = yy;
        yy = y;
      }
      width = xx-x0;
      height = yy-y0;
      // delay 200 ms then hide the canvas stage
      // set focused for taking screenshot
      waiting(200);
      stage.hide();
      disable(false);
      focused = true;
    });
3) When the START button is clicked, the virtual camera begins taking snapshots with a 20 ms timeframe from the computer screen (idle = false) and then “extracts” the focus area as a focused image. The button changes its text to STOP. When STOP is clicked, the virtual camera recording ends (done = false).
Java:
    ForkJoinPool.commonPool().execute(() -> {
      BufferedImage bImg, image;
      while (running) {
        while (idle)
          waiting(100);
        while (!done) {
          try { // taking snapshots
            bImg = (new java.awt.Robot()).
                   createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
            image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            image.setRGB(0, 0, width, height,
                         bImg.getRGB(x0, y0, width, height, null, 0, width),
                         0, width);
            bImgList.add(image);
            TimeUnit.MILLISECONDS.sleep(20);
          } catch (Exception ex) { }
        }
      }
    });
Note: There're two ways to copy pixels from one image to another image:
- the traditional way: slow, but easy to read
Java:
            for (int y = y0, b = 0; y < hMx && b < height; ++y, ++b)
              for (int x = x0, a = 0; x < wMx && a < width; ++x, ++a)
                image.setRGB(a, b, bImg.getRGB(x,y));
- the most effective way: read an array of Source (here: bImg) and add it to the new image (here: image)
Java:
            image.setRGB(0, 0, width, height,
                         bImg.getRGB(x0, y0, width, height, null, 0, width), // Source RGB array
                         0, width);
It depends on your coding purpose. The 2nd way is not only effective, but also very fast. For a time frame of this virtual camera, it is crucial to copy the pixels as quickly as possible.

The captured images are stored in an ArrayList<BufferedImage>. This list can be edited, showed, or saved as a new GIF or GZIP file (suffix .gif or .gzip). The images in Session 3 give you an idea of how the virtual camera process works. Before I go into more detail about GzipWriter.java, GifWriter.java and Display.java, let's take a look at Tools.java.Tools.java provides some convenient static methods that are frequently invoked by CamCorder.java. These static methods are:

1) getButton: Create JFX button with/without icon and with functional hint
2) getSlider: Create a JFX slider with a scale between 0.1 and 2.0
3) getTextField: Create JFX TextField with numeric check if input must be a number.
4) Dialog: Create JFX dialog frame
5) setPosition: positioning of the stage
6) getFile: get a file (with FileChooser)
7) toFiles: Creates n PNG image files from captured/loaded images
8) iClone: Clones an image

Note: The image here is BufferedImage (do not confuse with JFX Image).

(to be continued)
 
Sửa lần cuối:
Tham gia
15/1/24
Bài viết
10
Lượt Thích
5
Coins
3,790
(continuation)

The “SHOW” button (image from the 3rd session) opens a dialog in which you can specify how the captured/loaded images are displayed. To do this, it instantiates the object Display (Display.java), which displays the images in an animated GIF manner. This means that the image sequence repeats itself until you close the display window. The Display object is itself a full-fledged JFX class with its own Stage and Scene. The self-explanatory layout is JFX with Button, HBox and VBox. Therefore, there is no need to go into details.
Java:
public class Display {
  /**
  Display a BufferedImage array
  @param images ArrayList of buffered images
  @param tFrame int, the time frame between images
  */
  public Display(List<BufferedImage> images, int tFrame) {
    if (images.size() == 0) {
      Tools.dialog(null, false, "List of BufferedImages is empty.");
      return;
    }
    this.tFrame = tFrame;
    this.images = images;
    //    
    popup = new Stage();
    popup.setOnCloseRequest(ev -> {
      closed = true;
      popup.close();
    });
    popup.setTitle("Playing Images");
    BufferedImage img = images.get(0);
    height = img.getHeight();
    width = img.getWidth();
    //
    Canvas canvas = new Canvas(width, height);
    gc = canvas.getGraphicsContext2D( );
    gc.setFontSmoothingType(FontSmoothingType.GRAY);
    //
    Button next = Tools.getButton("NEXT", "Next Image");
    next.setOnAction(a -> {
      toggle = true;
      nxt = true;
      step = 1;
    });
    next.setDisable(true);
    //
    Button prev = Tools.getButton("PREV", "Previous Image");
    prev.setOnAction(a -> {
      toggle = true;
      nxt = true;
      step = -1;
    });
    prev.setDisable(true);
    //
    Button act = Tools.getButton("STOP", "Stop/Start playing");
    act.setOnAction(a -> {
      stop = !stop;
        step = 0;
      if (stop) {
        next.setDisable(false);
        prev.setDisable(false);
        act.setText("START");
        toggle = false;
      } else {
        next.setDisable(true);
        prev.setDisable(true);
        act.setText("STOP");
        toggle = true;
        nxt = true;
      }
    });
    HBox hbox = new HBox(5);
    hbox.setAlignment(Pos.CENTER);
    hbox.setPadding(new Insets(5, 5, 5, 5));
    hbox.getChildren().addAll(prev, act, next);
    //
    VBox vbox = new VBox(5);
    vbox.setAlignment(Pos.CENTER);
    vbox.setPadding(new Insets(5, 5, 5, 5));
    vbox.getChildren().addAll(canvas, hbox);
      
    Scene scene = new Scene(vbox, width+10, height+60);
    scene.getStylesheets().add("gif.css");
    popup.setScene(scene);
    popup.show();
    // start playing the images in ForkJoinPool
    ForkJoinPool.commonPool().execute(() -> {
      // repeat until closed is set
      for (int i = 0, mx = images.size()-1; !closed; ) {
        if (toggle) {
          gc.drawImage(SwingFXUtils.toFXImage(images.get(i), null), 0, 0);
          if (step == 0) try {
            ++i; // next image
            TimeUnit.MILLISECONDS.sleep(tFrame);
          } catch (Exception ex) { }
          else { // Stepping
            i += step;
            nxt = false;
            while (!nxt) ; // do nothing
          }
        } else {
          gc.drawImage(SwingFXUtils.toFXImage(images.get(i), null), 0, 0);
          while (!toggle) ; // do nothing
        }
        if (i < 0) i = mx;
        else if (i > mx) i = 0;
      }
    });
  }
  //
  private Stage popup;
  private GraphicsContext gc;
  private volatile int step = 0;
  private int tFrame, height, width;
  private List<BufferedImage> images;
  private volatile boolean stop = false, on = false, closed = false, toggle = true, nxt = true;
}
The last line "ForkJoinPool.commonPool().execute(() -> {...});" makes sure that Display executes the animation in the ForkJoinPool (concurrency/work-stealing mode).

The STOP button, which changes to START when pressed. When STOP has been pressed, NEXT and PREV button appear, allowing you to continue step by step - either forward (NEXT) or backward (PREV). START continues the show where it currently stands.
(to be continued)
 
Tham gia
15/1/24
Bài viết
10
Lượt Thích
5
Coins
3,790
(Continuation)
Last part.
Today I will briefly discuss with you about the writers: GzipWriter.java and GifWriter.java.

GzipWriter and GifWriter are specifically designed to store BufferedImages in a file (.gzip or .gif) using standard Java APIs:
- GZIPInputStream
- GZIPOutputStream
- ImageReader
-ImageWriter
Since images are usually large, they usually take a long time to save and load. During this time, all buttons except the QUIT button are disabled. So don't panic if this is the case. Both writers can either be instantiated or called directly via the provided static methods.

GifWriter:
Java:
  /**
  @param tF int, time frame in milliseconds between 100 ... 50
  @param zR float, Zooming Ratio between 2.0 .. 0.1
  @param imgLst ArrayList of BufferedImages
  @param outFile String, the outputfile.gif
  @param loop , 0: loop repeatedly, 1: no loop
  */
  public static void createGIF(int tF, float zR, ArrayList<BufferedImage> imgLst,
                               String outFile, int loop) throws Exception {

    ....
  }
and GzipWriter:
Java:
  /**
  @param zR float, Zooming Ratio between 2.0 .. 0.1
  @param imgLst ArrayList of BufferedImages
  @param outFile String, the outputfile.gzip
  */
  public static void createGZIP(float zR, ArrayList<BufferedImage> imgLst,
                               String outFile) throws Exception {
  ...
  }
and the invocation is
Java:
    try {
      if (fName.toLowerCase().indexOf(".gif") < 0)
           GzipWriter.createGZIP(ratio, al, fName);
      else GifWriter.createGIF(100, ratio, al, fName, 0);
      //
      int n = fName.lastIndexOf(File.separator);
      if (n > 0) dir = fName.substring(0, n);
    } catch (Exception ex) {
      Tools.dialog(panel, false, ex.toString());
    }
To beautify the appearance, JAVA JFX allows the inclusion of CSS (click HERE:https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html). CSS stands for Cascading Style Sheets. The CSS reference is a bit awkward. However, if you are well versed with CSS and JFX you will have no difficulty creating a CSS file. The rules are relatively simple. For example: a CheckBox is in CSS as:
CSS:
.check-box {
  -fx-font-size:12px;
  -fx-font-weight: bold;
  -fx-text-fill:#333333;
}
// or ComboBox
.combo-box .cell {
    -fx-text-fill: blue;
    -fx-font-size: 12px;
    -fx-font-weight: bold;
}
The keyword is then .check-box.
The JAR file is created by JDK 9.0.4+11. I choose this JDK version because the JFX packages are part of the JDK. Download here
Have fun
Click HERE for download CamCorder package
Note: the zip file contains a batch file bjar.X. Rename it to bjar.bat then run it.
Joe
 
Sửa lần cuối:
Tham gia
15/1/24
Bài viết
10
Lượt Thích
5
Coins
3,790
The link provided has an expiration date: January 28, 2024. If you missed the date, give me your email address HERE and I will send you the ZIP file.
 
Top Bottom
AdBlock Detected

We get it, advertisements are annoying!

Sure, ad-blocking software does a great job at blocking ads, but it also blocks useful features of our website. For the best site experience please disable your AdBlocker.

I've Disabled AdBlock
No Thanks