How to Resize Image in Java

Java has built-in API for resizing images in java.awt package. The code example below uses the Graphics2D and BufferedImage classes to resize all images in a directory. The ImageIO class supports a number of file formats such as jpeg, png, gif and bmp. The code below uses a percentage value for resizing the image. This ensures that the aspect ratio of the image is maintained during image resize.

When images are written to the destination folder, the extensions of source files are changed to the format of the output files. This means that you cannot have more than one file with same name but different extension in the source folder. If they are present in source folder, change the removeExtension() method to allow for duplicate names.

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;


// Resizes all images in a folder to another folder
// IMPORTANT: Cannot have same file name with different extensions in the source folder.
public class ImageResizer {

    public static void main(String[] args) throws Exception {
        ImageResizer resizer = new ImageResizer();
        ImageOutputConfig config = new ImageOutputConfig();

        // Quality hints to Graphics2D
        RenderingHints hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        config.hints = hints;
        config.formatName = "jpg"; // use jpg, bmp, gif etc.
        config.resizePercentage = 50; // Resize to 50%

        String sourceDir = "/Users/jj/source-images/";
        String destDir = "/Users/jj/dest-images/";

        resizer.resizeAllImages(sourceDir, destDir, config);
    }

    // Java program to resize all images in the specified folder
    // Ignores all hidden files.
    // IMPORTANT: If source folder contains identical file names with different extensions only one appear in output
    public void resizeAllImages(String sourceDirName, String destDirName, ImageOutputConfig config) throws Exception {
        File sourceDir = new File(sourceDirName);
        File destFolder = new File(destDirName);
        destFolder.mkdir(); // create destination if missing
        File[] sourceNames = sourceDir.listFiles();

        BufferedImage sourceImage;
        BufferedImage outputImage;
        File newFile;
        for (int i = 0; i < sourceNames.length; i++) {
            if (sourceNames[i].isFile() && !sourceNames[i].isHidden()) {
                sourceImage = ImageIO.read(new File(sourceDirName + sourceNames[i].getName()));
                outputImage = resizeImage(sourceImage, config);

                newFile = new File(destDirName + removeExtension(sourceNames[i]) + "." + config.formatName);
                ImageIO.write(outputImage, config.formatName, newFile);
            }
        }
    }

    // Resizes the image using the configuration passed in
    private BufferedImage resizeImage(BufferedImage source, ImageOutputConfig config) {
        int newHeight = (int) (source.getHeight() * config.resizePercentage / 100);
        int newWidth = (int) (source.getWidth() * config.resizePercentage / 100);
        
        // PNG supports transparency
        int type = config.formatName.equals("png")?BufferedImage.TYPE_INT_ARGB:BufferedImage.TYPE_INT_RGB;
        
        BufferedImage outputImage = new BufferedImage(newWidth, newHeight, type);
        Graphics2D graphics2D = outputImage.createGraphics();
        if (config.hints != null) {
            graphics2D.setRenderingHints(config.hints);
        }
        graphics2D.drawImage(source, 0, 0, newWidth, newHeight, null);
        graphics2D.dispose();
        return outputImage;
    }

    // Removes file extension. The new extension depends on format name
    private String removeExtension(File file) {
        String name = file.getName();
        int pos = name.lastIndexOf(".");
        if (pos > 0) {
            name = name.substring(0, pos);
        }
        return name;
    }
}

// Wrapper for image ouput parameters
class ImageOutputConfig {
    String formatName;
    double resizePercentage;
    RenderingHints hints;
}

The quality of the output image generated by the above program may not useful for many use cases. In such cases, it is recommended to use a third party library such as this one.

How to Open a New Window in JavaFX

If you are just getting started on JavaFX, Please read this JavaFX tutorial first. Usually a non trivial JavaFX application will have more than one window(stage). The following Java example shows how to open a new window in JavaFX. It also shows how to pass data from one stage(window) to another stage.

Please note that the JavaFX program below requires Java 1.8 update 40 or above. I recommend that you download the latest version of Java 8 before starting JavaFX development. A lot of new features such as Alert dialogs were added in JDK 8u40.

The sample JavaFX program below demonstrates the following features,

  • How to use Alert dialog display
  • How to open a new window(stage)
  • How to use the StackPane and VBox layouts
  • How to center align JavaFX layout content
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class JavaFXWindowDemo extends Application {

    private String loggedInUser;

    public static void main(String[] args) {
        launch(args);
    }

    // JavaFX entry point
    // How to open a new window in JavaFX
    @Override
    public void start(Stage primaryStage) throws Exception {
        Button btnLogin = new Button();
        btnLogin.setText("Login");
        btnLogin.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                showLoginScreen();
            }
        });
        // A layout container for UI controls
        StackPane root = new StackPane();
        root.getChildren().add(btnLogin);

        // Top level container for all view content
        Scene scene = new Scene(root, 300, 250);

        // primaryStage is the main top level window created by platform
        primaryStage.setTitle("JavaFX Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public void setLoggedInUser(String user) {
        loggedInUser = user;

        Alert alert = new Alert(AlertType.INFORMATION);
        alert.setTitle("Successful login");
        alert.setHeaderText("Successful login");
        String s = user + " logged in!";
        alert.setContentText(s);
        alert.show();
    }

    public void showLoginScreen() {
        Stage stage = new Stage();

        VBox box = new VBox();
        box.setPadding(new Insets(10));

        // How to center align content in a layout manager in JavaFX
        box.setAlignment(Pos.CENTER);

        Label label = new Label("Enter username and password");

        TextField textUser = new TextField();
        textUser.setPromptText("enter user name");
        TextField textPass = new TextField();
        textPass.setPromptText("enter password");

        Button btnLogin = new Button();
        btnLogin.setText("Login");

        btnLogin.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                // Assume success always!
                setLoggedInUser(textUser.getText());
                stage.close(); // return to main window
            }
        });

        box.getChildren().add(label);
        box.getChildren().add(textUser);
        box.getChildren().add(textPass);
        box.getChildren().add(btnLogin);
        Scene scene = new Scene(box, 250, 150);
        stage.setScene(scene);
        stage.show();
    }
}

How to Rotate an Image Using Affine Transform in Java

Java awt package contains a number of classes for image processing. AffineTransform class can be used for a number of 2D graphics processing requirements. The following Java source code demonstrates the use of AffineTransform to perform 90 degree image rotations. This example preserves the full image after the rotation. If you use the following code for arbitrary rotations, the image may be cut off.

How to Rotate an Image Using Affine Transform in Java

For 90 degree rotations program sets the new image width to the height of the old image and the new image height to the width of the old image. Since the rotation anchor point is is the center of the image, we move the image (using translate transform) depending on whether we rotate clockwise or counter clockwise.

For performing arbitrary rotations, new image dimensions and translation requires mathematical calculations.

import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;

// Rotates an image 90 degrees clockwise/counter clockwise using AffineTransform in Java
// Preserves the full image.
public class ImageRotationDemo {
    private static final String PATH_TO_IMAGE="/Users/jj/";
    private static final String INPUT_FILE_NAME="input.png";
    private static final String OUTPUT_FILE_NAME = "output.png";
    
    public static void main(String[] args ) throws Exception{
        ImageRotationDemo demo = new ImageRotationDemo();
        demo.rotateImage();
    }

    private void rotateImage() throws Exception {
        BufferedImage source = ImageIO.read(new File(PATH_TO_IMAGE+INPUT_FILE_NAME));
        
        BufferedImage output = new BufferedImage(source.getHeight(), source.getWidth(), source.getType());
           
        AffineTransformOp op = new AffineTransformOp(rotateCounterClockwise90(source), AffineTransformOp.TYPE_BILINEAR);
        op.filter(source, output);

        ImageIO.write(output, "png", new File(PATH_TO_IMAGE+OUTPUT_FILE_NAME));
        
    }
    
    // Rotates clockwise 90 degrees. Uses rotation on center and then translating it to origin
    private AffineTransform rotateClockwise90(BufferedImage source) {
        AffineTransform transform = new AffineTransform();
        transform.rotate(Math.PI/2, source.getWidth()/2, source.getHeight()/2);
        double offset = (source.getWidth()-source.getHeight())/2;
        transform.translate(offset,offset);
        return transform;
    }
    
    // Rotates counter clockwise 90 degrees. Uses rotation on center and then translating it to origin
    private AffineTransform rotateCounterClockwise90(BufferedImage source) {
        AffineTransform transform = new AffineTransform();
        transform.rotate(-Math.PI/2, source.getWidth()/2, source.getHeight()/2);
        double offset = (source.getWidth()-source.getHeight())/2;
        transform.translate(-offset,-offset);
        return transform;
    }
    
}

How to Securely Store Passwords in Java

One of the common requirements in Java web application is the secure storage of user passwords. You should never store user passwords in plain text. Passwords must be stored in such a way that there should be no way of getting the original password from the stored representation. Cryptographic one way hash functions are perfect for this purpose. Same hash is generated for the same password, but the original password cannot retrieved from the hash alone.

There are a number of common hash functions in use such as MD5, SHA1, SHA256 etc. I have also seen many implementations using them directly for password storage. However these are not suitable for password hashes due to a number of reasons - the algorithms such as SHA256 are too fast and hence brute forcing the hash is easy. Using MD5 hash without a salt is almost as bad as storing plain text passwords!

There are a number of hash functions specifically designed for storing hashed passwords. These include PBKDF2, bcrypt, scrypt etc. PBKDF2 is an excellent hash algorithm for password hashing and is one of the NIST recommended algorithms.

Verifiers SHALL store memorized secrets in a form that is resistant to offline attacks. Secrets SHALL be hashed with a salt value using an approved hash function such as PBKDF2 as described in [SP 800-132]. The salt value SHALL be a 32-bit or longer random value generated by an approved random bit generator and stored along with the hash result. At least 10,000 iterations of the hash function SHOULD be performed. A keyed hash function (e.g., HMAC [FIPS198-1]), with the key stored separately from the hashed authenticators (e.g., in a hardware security module) SHOULD be used to further resist dictionary attacks against the stored hashed authenticators.

One of the issues with hash functions is that they are susceptible to rainbow table attack. This attack can be prevented by using a salt text (a random text) along with each password before hashing it. This ensures that even when two different users use the same password, the hash values stored are different.

The following Java program demonstrates the use of PBKDF2 hash algorithm with salt text for storing passwords. It uses NIST recommended specification. The following example will run on Java SE 8 or above. If you want to use it Java 7 or below replace the Base64 class with Apache commons codec Base64 class.

Please note that the salt stored for the user must be changed when password is updated.

import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class SecurePasswordStorageDemo {

    // Simulates database of users!
    private Map<String, UserInfo> userDatabase = new HashMap<String,UserInfo>();

    public static void main(String[] args) throws Exception {
        SecurePasswordStorageDemo passManager = new SecurePasswordStorageDemo();
        String userName = "admin";
        String password = "password";
        passManager.signUp(userName, password);

        Scanner scanner = new Scanner(System.in);
        System.out.println("Please enter username:");
        String inputUser = scanner.nextLine();

        System.out.println("Please enter password:");
        String inputPass = scanner.nextLine();

        boolean status = passManager.authenticateUser(inputUser, inputPass);
        if (status) {
            System.out.println("Logged in!");
        } else {
            System.out.println("Sorry, wrong username/password");
        }
        scanner.close();
    }

    private boolean authenticateUser(String inputUser, String inputPass) throws Exception {
        UserInfo user = userDatabase.get(inputUser);
        if (user == null) {
            return false;
        } else {
            String salt = user.userSalt;
            String calculatedHash = getEncryptedPassword(inputPass, salt);
            if (calculatedHash.equals(user.userEncryptedPassword)) {
                return true;
            } else {
                return false;
            }
        }
    }

    private void signUp(String userName, String password) throws Exception {
        String salt = getNewSalt();
        String encryptedPassword = getEncryptedPassword(password, salt);
        UserInfo user = new UserInfo();
        user.userEncryptedPassword = encryptedPassword;
        user.userName = userName;
        user.userSalt = salt;
        saveUser(user);
    }

    // Get a encrypted password using PBKDF2 hash algorithm
    public String getEncryptedPassword(String password, String salt) throws Exception {
        String algorithm = "PBKDF2WithHmacSHA1";
        int derivedKeyLength = 160; // for SHA1
        int iterations = 20000; // NIST specifies 10000

        byte[] saltBytes = Base64.getDecoder().decode(salt);
        KeySpec spec = new PBEKeySpec(password.toCharArray(), saltBytes, iterations, derivedKeyLength);
        SecretKeyFactory f = SecretKeyFactory.getInstance(algorithm);

        byte[] encBytes = f.generateSecret(spec).getEncoded();
        return Base64.getEncoder().encodeToString(encBytes);
    }

    // Returns base64 encoded salt
    public String getNewSalt() throws Exception {
        // Don't use Random!
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        // NIST recommends minimum 4 bytes. We use 8.
        byte[] salt = new byte[8];
        random.nextBytes(salt);
        return Base64.getEncoder().encodeToString(salt);
    }

    private void saveUser(UserInfo user) {
        userDatabase.put(user.userName, user);
    }

}

// Each user has a unique salt
// This salt must be recomputed during password change!
class UserInfo {
    String userEncryptedPassword;
    String userSalt;
    String userName;
}

How to Write a Coin Toss Game in Java

Check out the following source code for a simple coin toss game written in Java. This game program illustrates the use of Random class and enumerators in Java. The program asks the user to guess the coin toss and then compares the value with the actual coin toss result. Coin toss program runs the game in an infinite loop until the user decides to quit by entering q.

import java.util.Random;
import java.util.Scanner;

// Coin toss/flip game in Java
public class CoinTossGame {

    private enum Coin {
        Head, Tail
    };

    public static void main(String[] args) {
        CoinTossGame game = new CoinTossGame();
        game.startGame();
    }

    // Starts a coin flip game till user decides to quit.
    private void startGame() {

        Scanner scanner = new Scanner(System.in);
        Coin guess;

        while (true) {
            System.out.print("Enter you guess (h for heads, t for tails, q to quit):");
            String choice = scanner.nextLine();
            if (choice.equalsIgnoreCase("q")) {
                break;
            } else if (choice.equalsIgnoreCase("h")) {
                guess = Coin.Head;
            } else if (choice.equalsIgnoreCase("t")) {
                guess = Coin.Tail;
            } else {
                System.out.println("Wrong choice! Try again!");
                continue;
            }

            Coin toss = tossCoin();
            if (guess == toss) {
                System.out.println("Congratulations! You won the toss!");
            } else {
                System.out.println("Sorry! You lost the toss.");
            }

        }
        scanner.close();

    }

    // Flip a coin and return result
    private Coin tossCoin() {
        Random r = new Random();
        int i = r.nextInt(2);
        if (i == 0) {
            return Coin.Head;
        } else {
            return Coin.Tail;
        }
    }

}

Here is a coin toss game in action!

$ java CoinTossGame
Enter you guess (h for heads, t for tails, q to quit):h
Congratulations! You won the toss!
Enter you guess (h for heads, t for tails, q to quit):t
Congratulations! You won the toss!
Enter you guess (h for heads, t for tails, q to quit):t
Congratulations! You won the toss!
Enter you guess (h for heads, t for tails, q to quit):h
Congratulations! You won the toss!
Enter you guess (h for heads, t for tails, q to quit):t
Congratulations! You won the toss!
Enter you guess (h for heads, t for tails, q to quit):h
Sorry! You lost the toss.
Enter you guess (h for heads, t for tails, q to quit):h
Sorry! You lost the toss.
Enter you guess (h for heads, t for tails, q to quit):t
Congratulations! You won the toss!
Enter you guess (h for heads, t for tails, q to quit):q
$