13

As our network based applications fall in love with webp image formats, I found my self in need of a method or a lib which can decode it,

I have written this piece of code, but it only misses the native decoder(how ever I prefer it to be a jar lib) :

public BufferedImage decodeWebP(byte[] encoded, int w, int h) {
    int[] width = new int[]{w};
    int[] height = new int[]{h};

    byte[] decoded = decodeRGBAnative(encoded);  //here is the missing part , 
    if (decoded.length == 0) return null;

    int[] pixels = new int[decoded.length / 4];
    ByteBuffer.wrap(decoded).asIntBuffer().get(pixels);

    BufferedImage bufferedImage = new BufferedImage(width[0], height[0], BufferedImage.TYPE_INT_RGB);

    //  bufferedImage.setRGB(x, y, your_value);

    int BLOCK_SIZE = 3;

    for(int r=0; r< height[0]; r++) {
        for (int c = 0; c < width[0]; c++) {
            int index = r * width[0] * BLOCK_SIZE + c * BLOCK_SIZE;
            int red = pixels[index] & 0xFF;
            int green = pixels[index + 1] & 0xFF;
            int blue = pixels[index + 2] & 0xFF;
            int rgb = (red << 16) | (green << 8) | blue;
            bufferedImage.setRGB(c, r, rgb);
        }
    }
    return bufferedImage;
}
slavoo
  • 5,798
  • 64
  • 37
  • 39
Reza
  • 321
  • 2
  • 4
  • 14

5 Answers5

2

The bitbucket link of the original answer is not available anymore, but forks from the original repository can be found, in example: https://github.com/sejda-pdf/webp-imageio

I tried using the webp-imageio implementation from the mentioned github, but after 2 days of using it in production, I got a segmentation fault coming from the native library that crashed the whole server.

I resorted to using the compiled tools provided by google here: https://developers.google.com/speed/webp/download and do a small wrapper class to call the binaries.

In my case, I needed to convert from other images formats to webp, so I needed the "cwebp" binary. The wrapper I wrote is:

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.concurrent.TimeUnit;

public class ImageWebpLibraryWrapper {

  private static final String CWEBP_BIN_PATH = "somepath/libwebp-1.1.0-linux-x86-64/bin/cwebp";

  public static boolean isWebPAvailable() {
    if ( CWEBP_BIN_PATH == null ) {
      return false;
    }
    return new File( CWEBP_BIN_PATH ).exists();
  }

  public static boolean convertToWebP( File imageFile, File targetFile, int quality ) {
    Process process;
    try {
      process = new ProcessBuilder( CWEBP_BIN_PATH, "-q", "" + quality, imageFile.getAbsolutePath(), "-o",
          targetFile.getAbsolutePath() ).start();
      process.waitFor( 10, TimeUnit.SECONDS );
      if ( process.exitValue() == 0 ) {
        // Success
        printProcessOutput( process.getInputStream(), System.out );
        return true;
      } else {
        printProcessOutput( process.getErrorStream(), System.err );
        return false;
      }
    } catch ( Exception e ) {
      e.printStackTrace();
      return false;
    }
  }
  
  private static void printProcessOutput( InputStream inputStream, PrintStream output ) throws IOException {
    try ( InputStreamReader isr = new InputStreamReader( inputStream );
        BufferedReader bufferedReader = new BufferedReader( isr ) ) {
      String line;
      while ( ( line = bufferedReader.readLine() ) != null ) {
        output.println( line );
      }
    }
  }

An implementation around ImageIO is nicer, but I couldn't have a segmentation fault crashing the production server.

Sample usage:

public static void main( String args[] ) {
  if ( !isWebPAvailable() ) {
    System.err.println( "cwebp binary not found!" );
    return;
  }
  File file = new File( "/home/xxx/Downloads/image_11.jpg" );
  File outputFile = new File( "/home/xxx/Downloads/image_11-test.webp" );
  int quality = 80;
  if ( convertToWebP( file, outputFile, quality ) ) {
    System.out.println( "SUCCESS" );
  } else {
    System.err.println( "FAIL" );
  }
}
Aldo Canepa
  • 1,791
  • 2
  • 16
  • 16
  • There is a problem reading the output + error streams after the process terminated. If the stream buffer runs full your process will block. It is better to instantly stream the content of both streams while waiting or using a pipe. – Martin Kersten Mar 23 '21 at 23:14
  • Hmm, that sounds bad. Could you post some code on how to handle the streams the way you mention? Or elaborate a bit on how to reproduce the problem? I tried calling the method to convert to webp on a file that causes an error, did it 10.000 times and didn't see any problem. – Aldo Canepa Mar 25 '21 at 00:50
  • You can create a batch file writing a lot of output and execute it as a process from within a Java app. The problem arises if you have a process that prints a lot of output. If your process does not do that you will be fine. It also depends on the OS / Java buffer for the output. I had this problem like 5 or so years ago. It might have been fixed but I doubt it (as there is no fix you can only push the boundary when the problem happens). – Martin Kersten Mar 27 '21 at 10:34
  • 1
    A solution is to not use process.waitFor but spin yourself on process.isAlive. When it is still running you can use both streams and ask if bytes are available and remove those from the stream and print them out (or whatever). This way these streams will never overflow. – Martin Kersten Mar 27 '21 at 10:36
2

Please use OpenCV. I use maven and org.openpnp:opencv:4.5.1-2. All it takes to encode an image that is stored in a Mat is:

public static byte [] encodeWebp(Mat image, int quality) {
    MatOfInt parameters = new MatOfInt(Imgcodecs.IMWRITE_WEBP_QUALITY, quality);
    MatOfByte output = new MatOfByte();
    if(Imgcodecs.imencode(".webp", image, output, parameters)) 
        return output.toArray();
    else
        throw new IllegalStateException("Failed to encode the image as webp with quality " + quality);
}

I am converting it to byte [] arrays since I store it mostly in S3 and DB and rather sheldom in the filesystem.

Martin Kersten
  • 5,127
  • 8
  • 46
  • 77
1

Used OpenPnP OpenCV in kotlin:

fun encodeToWebP(image: ByteArray): ByteArray {
        val matImage = Imgcodecs.imdecode(MatOfByte(*image), Imgcodecs.IMREAD_UNCHANGED)
        val parameters = MatOfInt(Imgcodecs.IMWRITE_WEBP_QUALITY, 100)
        val output = MatOfByte()
        if (Imgcodecs.imencode(".webp", matImage, output, parameters)) {
            return output.toArray()
        } else {
            throw IllegalStateException("Failed to encode the image as webp")
        }
    }
Dušan Salay
  • 309
  • 1
  • 12
0

among all searches possible , this one was best and easiest :

https://bitbucket.org/luciad/webp-imageio

not full java implemention , but very easy comparing to others

Reza
  • 321
  • 2
  • 4
  • 14
0

for java developers, who are coming from a search engine, I have converted @Dušan Salay answer to java:

private byte[] encodeToWebP(byte[] data) {
    Mat matImage = Imgcodecs.imdecode(new MatOfByte(data), Imgcodecs.IMREAD_UNCHANGED);
    MatOfInt parameters = new MatOfInt(Imgcodecs.IMWRITE_WEBP_QUALITY, 100);
    MatOfByte output = new MatOfByte();
    Imgcodecs.imencode(".webp", matImage, output, parameters);
    return output.toArray();
}

I have used apache commons to readFileToByteArray. Also you will need to load library first in static block

static {
    OpenCV.loadLocally();
}
Ahmed Abbas
  • 118
  • 8