webp-imageio-core/src/main/java/com/luciad/imageio/webp/WebP.java

429 lines
16 KiB
Java

/*
* Copyright 2013 Luciad (http://www.luciad.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.luciad.imageio.webp;
import org.scijava.nativelib.NativeLibraryUtil;
import java.awt.image.*;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Hashtable;
final class WebP {
private static boolean NATIVE_LIBRARY_LOADED = false;
static synchronized void loadNativeLibrary() {
if ( !NATIVE_LIBRARY_LOADED ) {
NATIVE_LIBRARY_LOADED = true;
NativeLibraryUtil.loadNativeLibrary(WebP.class, "webp-imageio");
}
}
static {
loadNativeLibrary();
}
private WebP() {
}
public static BufferedImage decode( WebPReadParam aReadParam, byte[] aData, int aOffset, int aLength ) throws IOException {
if ( aReadParam == null ) {
throw new NullPointerException( "Decoder options may not be null" );
}
if ( aData == null ) {
throw new NullPointerException( "Input data may not be null" );
}
if ( aOffset + aLength > aData.length ) {
throw new IllegalArgumentException( "Offset/length exceeds array size" );
}
int[] out = new int[4];
int[] pixels = decode( aReadParam.fPointer, aData, aOffset, aLength, out, ByteOrder.nativeOrder().equals( ByteOrder.BIG_ENDIAN ) );
VP8StatusCode status = VP8StatusCode.getStatusCode( out[0] );
switch ( status ) {
case VP8_STATUS_OK:
break;
case VP8_STATUS_OUT_OF_MEMORY:
throw new OutOfMemoryError();
default:
throw new IOException( "Decode returned code " + status );
}
int width = out[1];
int height = out[2];
boolean alpha = out[3] != 0;
ColorModel colorModel;
if ( alpha ) {
colorModel = new DirectColorModel( 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 );
} else {
colorModel = new DirectColorModel( 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 );
}
SampleModel sampleModel = colorModel.createCompatibleSampleModel( width, height );
DataBufferInt db = new DataBufferInt( pixels, width * height );
WritableRaster raster = WritableRaster.createWritableRaster( sampleModel, db, null );
return new BufferedImage( colorModel, raster, false, new Hashtable<Object, Object>() );
}
private static native int[] decode( long aDecoderOptionsPointer, byte[] aData, int aOffset, int aLength, int[] aFlags, boolean aBigEndian );
public static int[] getInfo( byte[] aData, int aOffset, int aLength ) throws IOException {
int[] out = new int[2];
int result = getInfo( aData, aOffset, aLength, out );
if (result == 0) {
throw new IOException( "Invalid WebP data" );
}
return out;
}
private static native int getInfo( byte[] aData, int aOffset, int aLength, int[] aOut );
public static byte[] encode( WebPWriteParam aWriteParam, RenderedImage aImage ) throws IOException {
if ( aWriteParam == null ) {
throw new NullPointerException( "Encoder options may not be null" );
}
if ( aImage == null ) {
throw new NullPointerException( "Image may not be null" );
}
boolean encodeAlpha = hasTranslucency( aImage );
if ( encodeAlpha ) {
byte[] rgbaData = getRGBA( aImage );
return encodeRGBA( aWriteParam.getPointer(), rgbaData, aImage.getWidth(), aImage.getHeight(), aImage.getWidth() * 4 );
}
else {
byte[] rgbData = getRGB( aImage );
return encodeRGB( aWriteParam.getPointer(), rgbData, aImage.getWidth(), aImage.getHeight(), aImage.getWidth() * 3 );
}
}
private static native byte[] encodeRGBA( long aConfig, byte[] aRgbaData, int aWidth, int aHeight, int aStride );
private static native byte[] encodeRGB( long aConfig, byte[] aRgbaData, int aWidth, int aHeight, int aStride );
private static boolean hasTranslucency( RenderedImage aRi ) {
return aRi.getColorModel().hasAlpha();
}
private static int getShift( int aMask ) {
int shift = 0;
while ( ( ( aMask >> shift ) & 0x1 ) == 0 ) {
shift++;
}
return shift;
}
private static byte[] getRGB( RenderedImage aRi ) throws IOException {
int width = aRi.getWidth();
int height = aRi.getHeight();
ColorModel colorModel = aRi.getColorModel();
if ( colorModel instanceof ComponentColorModel ) {
ComponentSampleModel sampleModel = ( ComponentSampleModel ) aRi.getSampleModel();
int type = sampleModel.getTransferType();
if ( type == DataBuffer.TYPE_BYTE ) {
return extractComponentRGBByte( width, height, sampleModel, ( ( DataBufferByte ) aRi.getData().getDataBuffer() ) );
}
else if ( type == DataBuffer.TYPE_INT ) {
return extractComponentRGBInt( width, height, sampleModel, ( ( DataBufferInt ) aRi.getData().getDataBuffer() ) );
}
else {
throw new IOException( "Incompatible image: " + aRi );
}
}
else if ( colorModel instanceof DirectColorModel ) {
SinglePixelPackedSampleModel sampleModel = ( SinglePixelPackedSampleModel ) aRi.getSampleModel();
int type = sampleModel.getTransferType();
if ( type == DataBuffer.TYPE_INT ) {
return extractDirectRGBInt( width, height, ( DirectColorModel ) colorModel, sampleModel, ( ( DataBufferInt ) aRi.getData().getDataBuffer() ) );
}
else {
throw new IOException( "Incompatible image: " + aRi );
}
}
return extractGenericRGB( aRi, width, height, colorModel );
}
private static byte[] extractGenericRGB( RenderedImage aRi, int aWidth, int aHeight, ColorModel aColorModel ) {
Object dataElements = null;
byte[] rgbData = new byte[ aWidth * aHeight * 3 ];
for ( int b = 0, y = 0; y < aHeight; y++ ) {
for ( int x = 0; x < aWidth; x++, b += 3 ) {
dataElements = aRi.getData().getDataElements( x, y, dataElements );
rgbData[ b ] = ( byte ) aColorModel.getRed( dataElements );
rgbData[ b + 1 ] = ( byte ) aColorModel.getGreen( dataElements );
rgbData[ b + 2 ] = ( byte ) aColorModel.getBlue( dataElements );
}
}
return rgbData;
}
private static byte[] extractDirectRGBInt( int aWidth, int aHeight, DirectColorModel aColorModel, SinglePixelPackedSampleModel aSampleModel, DataBufferInt aDataBuffer ) {
byte[] out = new byte[ aWidth * aHeight * 3 ];
int rMask = aColorModel.getRedMask();
int gMask = aColorModel.getGreenMask();
int bMask = aColorModel.getBlueMask();
int rShift = getShift( rMask );
int gShift = getShift( gMask );
int bShift = getShift( bMask );
int[] bank = aDataBuffer.getBankData()[ 0 ];
int scanlineStride = aSampleModel.getScanlineStride();
int scanIx = 0;
for ( int b = 0, y = 0; y < aHeight; y++ ) {
int pixIx = scanIx;
for ( int x = 0; x < aWidth; x++, b += 3 ) {
int pixel = bank[ pixIx++ ];
out[ b ] = ( byte ) ( ( pixel & rMask ) >>> rShift );
out[ b + 1 ] = ( byte ) ( ( pixel & gMask ) >>> gShift );
out[ b + 2 ] = ( byte ) ( ( pixel & bMask ) >>> bShift );
}
scanIx += scanlineStride;
}
return out;
}
private static byte[] extractComponentRGBInt( int aWidth, int aHeight, ComponentSampleModel aSampleModel, DataBufferInt aDataBuffer ) {
byte[] out = new byte[ aWidth * aHeight * 3 ];
int[] bankIndices = aSampleModel.getBankIndices();
int[] rBank = aDataBuffer.getBankData()[ bankIndices[ 0 ] ];
int[] gBank = aDataBuffer.getBankData()[ bankIndices[ 1 ] ];
int[] bBank = aDataBuffer.getBankData()[ bankIndices[ 2 ] ];
int[] bankOffsets = aSampleModel.getBandOffsets();
int rScanIx = bankOffsets[ 0 ];
int gScanIx = bankOffsets[ 1 ];
int bScanIx = bankOffsets[ 2 ];
int pixelStride = aSampleModel.getPixelStride();
int scanlineStride = aSampleModel.getScanlineStride();
for ( int b = 0, y = 0; y < aHeight; y++ ) {
int rPixIx = rScanIx;
int gPixIx = gScanIx;
int bPixIx = bScanIx;
for ( int x = 0; x < aWidth; x++, b += 3 ) {
out[ b ] = ( byte ) rBank[ rPixIx ];
rPixIx += pixelStride;
out[ b + 1 ] = ( byte ) gBank[ gPixIx ];
gPixIx += pixelStride;
out[ b + 2 ] = ( byte ) bBank[ bPixIx ];
bPixIx += pixelStride;
}
rScanIx += scanlineStride;
gScanIx += scanlineStride;
bScanIx += scanlineStride;
}
return out;
}
private static byte[] extractComponentRGBByte( int aWidth, int aHeight, ComponentSampleModel aSampleModel, DataBufferByte aDataBuffer ) {
byte[] out = new byte[ aWidth * aHeight * 3 ];
int[] bankIndices = aSampleModel.getBankIndices();
byte[] rBank = aDataBuffer.getBankData()[ bankIndices[ 0 ] ];
byte[] gBank = aDataBuffer.getBankData()[ bankIndices[ 1 ] ];
byte[] bBank = aDataBuffer.getBankData()[ bankIndices[ 2 ] ];
int[] bankOffsets = aSampleModel.getBandOffsets();
int rScanIx = bankOffsets[ 0 ];
int gScanIx = bankOffsets[ 1 ];
int bScanIx = bankOffsets[ 2 ];
int pixelStride = aSampleModel.getPixelStride();
int scanlineStride = aSampleModel.getScanlineStride();
for ( int b = 0, y = 0; y < aHeight; y++ ) {
int rPixIx = rScanIx;
int gPixIx = gScanIx;
int bPixIx = bScanIx;
for ( int x = 0; x < aWidth; x++, b += 3 ) {
out[ b ] = rBank[ rPixIx ];
rPixIx += pixelStride;
out[ b + 1 ] = gBank[ gPixIx ];
gPixIx += pixelStride;
out[ b + 2 ] = bBank[ bPixIx ];
bPixIx += pixelStride;
}
rScanIx += scanlineStride;
gScanIx += scanlineStride;
bScanIx += scanlineStride;
}
return out;
}
private static byte[] getRGBA( RenderedImage aRi ) throws IOException {
int width = aRi.getWidth();
int height = aRi.getHeight();
ColorModel colorModel = aRi.getColorModel();
if ( colorModel instanceof ComponentColorModel ) {
ComponentSampleModel sampleModel = ( ComponentSampleModel ) aRi.getSampleModel();
int type = sampleModel.getTransferType();
if ( type == DataBuffer.TYPE_BYTE ) {
return extractComponentRGBAByte( width, height, sampleModel, ( ( DataBufferByte ) aRi.getData().getDataBuffer() ) );
}
else if ( type == DataBuffer.TYPE_INT ) {
return extractComponentRGBAInt( width, height, sampleModel, ( ( DataBufferInt ) aRi.getData().getDataBuffer() ) );
}
else {
throw new IOException( "Incompatible image: " + aRi );
}
}
else if ( colorModel instanceof DirectColorModel ) {
SinglePixelPackedSampleModel sampleModel = ( SinglePixelPackedSampleModel ) aRi.getSampleModel();
int type = sampleModel.getTransferType();
if ( type == DataBuffer.TYPE_INT ) {
return extractDirectRGBAInt( width, height, ( DirectColorModel ) colorModel, sampleModel, ( ( DataBufferInt ) aRi.getData().getDataBuffer() ) );
}
else {
throw new IOException( "Incompatible image: " + aRi );
}
}
return extractGenericRGBA( aRi, width, height, colorModel );
}
private static byte[] extractGenericRGBA( RenderedImage aRi, int aWidth, int aHeight, ColorModel aColorModel ) {
Object dataElements = null;
byte[] rgbData = new byte[ aWidth * aHeight * 4 ];
for ( int b = 0, y = 0; y < aHeight; y++ ) {
for ( int x = 0; x < aWidth; x++, b += 4 ) {
dataElements = aRi.getData().getDataElements( x, y, dataElements );
rgbData[ b ] = ( byte ) aColorModel.getRed( dataElements );
rgbData[ b + 1 ] = ( byte ) aColorModel.getGreen( dataElements );
rgbData[ b + 2 ] = ( byte ) aColorModel.getBlue( dataElements );
rgbData[ b + 3 ] = ( byte ) aColorModel.getAlpha( dataElements );
}
}
return rgbData;
}
private static byte[] extractDirectRGBAInt( int aWidth, int aHeight, DirectColorModel aColorModel, SinglePixelPackedSampleModel aSampleModel, DataBufferInt aDataBuffer ) {
byte[] out = new byte[ aWidth * aHeight * 4 ];
int rMask = aColorModel.getRedMask();
int gMask = aColorModel.getGreenMask();
int bMask = aColorModel.getBlueMask();
int aMask = aColorModel.getAlphaMask();
int rShift = getShift( rMask );
int gShift = getShift( gMask );
int bShift = getShift( bMask );
int aShift = getShift( aMask );
int[] bank = aDataBuffer.getBankData()[ 0 ];
int scanlineStride = aSampleModel.getScanlineStride();
int scanIx = 0;
for ( int b = 0, y = 0; y < aHeight; y++ ) {
int pixIx = scanIx;
for ( int x = 0; x < aWidth; x++, b += 4 ) {
int pixel = bank[ pixIx++ ];
out[ b ] = ( byte ) ( ( pixel & rMask ) >>> rShift );
out[ b + 1 ] = ( byte ) ( ( pixel & gMask ) >>> gShift );
out[ b + 2 ] = ( byte ) ( ( pixel & bMask ) >>> bShift );
out[ b + 3 ] = ( byte ) ( ( pixel & aMask ) >>> aShift );
}
scanIx += scanlineStride;
}
return out;
}
private static byte[] extractComponentRGBAInt( int aWidth, int aHeight, ComponentSampleModel aSampleModel, DataBufferInt aDataBuffer ) {
byte[] out = new byte[ aWidth * aHeight * 4 ];
int[] bankIndices = aSampleModel.getBankIndices();
int[] rBank = aDataBuffer.getBankData()[ bankIndices[ 0 ] ];
int[] gBank = aDataBuffer.getBankData()[ bankIndices[ 1 ] ];
int[] bBank = aDataBuffer.getBankData()[ bankIndices[ 2 ] ];
int[] aBank = aDataBuffer.getBankData()[ bankIndices[ 3 ] ];
int[] bankOffsets = aSampleModel.getBandOffsets();
int rScanIx = bankOffsets[ 0 ];
int gScanIx = bankOffsets[ 1 ];
int bScanIx = bankOffsets[ 2 ];
int aScanIx = bankOffsets[ 3 ];
int pixelStride = aSampleModel.getPixelStride();
int scanlineStride = aSampleModel.getScanlineStride();
for ( int b = 0, y = 0; y < aHeight; y++ ) {
int rPixIx = rScanIx;
int gPixIx = gScanIx;
int bPixIx = bScanIx;
int aPixIx = aScanIx;
for ( int x = 0; x < aWidth; x++, b += 4 ) {
out[ b ] = ( byte ) rBank[ rPixIx ];
rPixIx += pixelStride;
out[ b + 1 ] = ( byte ) gBank[ gPixIx ];
gPixIx += pixelStride;
out[ b + 2 ] = ( byte ) bBank[ bPixIx ];
bPixIx += pixelStride;
out[ b + 3 ] = ( byte ) aBank[ aPixIx ];
aPixIx += pixelStride;
}
rScanIx += scanlineStride;
gScanIx += scanlineStride;
bScanIx += scanlineStride;
aScanIx += scanlineStride;
}
return out;
}
private static byte[] extractComponentRGBAByte( int aWidth, int aHeight, ComponentSampleModel aSampleModel, DataBufferByte aDataBuffer ) {
byte[] out = new byte[ aWidth * aHeight * 4 ];
int[] bankIndices = aSampleModel.getBankIndices();
byte[] rBank = aDataBuffer.getBankData()[ bankIndices[ 0 ] ];
byte[] gBank = aDataBuffer.getBankData()[ bankIndices[ 1 ] ];
byte[] bBank = aDataBuffer.getBankData()[ bankIndices[ 2 ] ];
byte[] aBank = aDataBuffer.getBankData()[ bankIndices[ 3 ] ];
int[] bankOffsets = aSampleModel.getBandOffsets();
int rScanIx = bankOffsets[ 0 ];
int gScanIx = bankOffsets[ 1 ];
int bScanIx = bankOffsets[ 2 ];
int aScanIx = bankOffsets[ 3 ];
int pixelStride = aSampleModel.getPixelStride();
int scanlineStride = aSampleModel.getScanlineStride();
for ( int b = 0, y = 0; y < aHeight; y++ ) {
int rPixIx = rScanIx;
int gPixIx = gScanIx;
int bPixIx = bScanIx;
int aPixIx = aScanIx;
for ( int x = 0; x < aWidth; x++, b += 4 ) {
out[ b ] = rBank[ rPixIx ];
rPixIx += pixelStride;
out[ b + 1 ] = gBank[ gPixIx ];
gPixIx += pixelStride;
out[ b + 2 ] = bBank[ bPixIx ];
bPixIx += pixelStride;
out[ b + 3 ] = aBank[ aPixIx ];
aPixIx += pixelStride;
}
rScanIx += scanlineStride;
gScanIx += scanlineStride;
bScanIx += scanlineStride;
aScanIx += scanlineStride;
}
return out;
}
}