diff --git a/.gitignore b/.gitignore index 32858aa..0460bc2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,36 @@ -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar +# maven ignore +target/ *.war -*.ear +*.zip +*.tar +*.tar.gz -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* +# eclipse ignore +.settings/ +.project +.classpath +.tern-project + +# idea ignore +.idea/ +*.ipr +*.iml +*.iws + +# temp ignore +.svn/ +generated/ +.externalToolBuilders/ +logs/ +*.log +*.cache +*.diff +*.patch +*.tmp +*.java~ +*.properties~ +*.xml~ + +# system ignore +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/README.md b/README.md index 315e50b..e652840 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # j-webp Java Image I/O reader and writer for the Google WebP image format. + +基于[webp project of Luciad](https://bitbucket.org/luciad/webp-imageio) 0.4.2版本修改. + +实际上只修改了`com.luciad.imageio.webp.WebP.loadNativeLibrary`这一个方法. +因为按他默认的加载方式, 需要把native的so/dll/dylib等文件放到OS对应的`java.library.path`对应的目录才能加载到, 这会给部署带来一些不便. + +所以我们这里换成了[native-lib-loader](https://github.com/scijava/native-lib-loader)自动加载, 编译构建好的包里已经包含了各种OS上的native文件, 使用时会自动加载. + +具体使用方法可参看`src/main/java/example`下的源码. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..dcbafff --- /dev/null +++ b/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + net.wangq + webp + 1.0-SNAPSHOT + + + UTF-8 + 1.7 + 1.7 + + + + + org.scijava + native-lib-loader + 2.1.3 + + + + \ No newline at end of file diff --git a/src/main/java/com/luciad/imageio/webp/VP8StatusCode.java b/src/main/java/com/luciad/imageio/webp/VP8StatusCode.java new file mode 100644 index 0000000..0d1047e --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/VP8StatusCode.java @@ -0,0 +1,38 @@ +/* + * 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; + +enum VP8StatusCode { + VP8_STATUS_OK, + VP8_STATUS_OUT_OF_MEMORY, + VP8_STATUS_INVALID_PARAM, + VP8_STATUS_BITSTREAM_ERROR, + VP8_STATUS_UNSUPPORTED_FEATURE, + VP8_STATUS_SUSPENDED, + VP8_STATUS_USER_ABORT, + VP8_STATUS_NOT_ENOUGH_DATA,; + + private static VP8StatusCode[] VALUES = values(); + + public static VP8StatusCode getStatusCode( int aValue ) { + if ( aValue >= 0 && aValue < VALUES.length ) { + return VALUES[ aValue ]; + } + else { + return null; + } + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebP.java b/src/main/java/com/luciad/imageio/webp/WebP.java new file mode 100644 index 0000000..cdda853 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebP.java @@ -0,0 +1,428 @@ +/* + * 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() ); + } + + 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; + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPImageReaderSpi.java b/src/main/java/com/luciad/imageio/webp/WebPImageReaderSpi.java new file mode 100644 index 0000000..dc539da --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPImageReaderSpi.java @@ -0,0 +1,106 @@ +/* + * 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 javax.imageio.ImageReader; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Locale; + +/** + * + */ +public class WebPImageReaderSpi extends ImageReaderSpi { + private static final byte[] RIFF = new byte[]{ 'R', 'I', 'F', 'F' }; + private static final byte[] WEBP = new byte[]{ 'W', 'E', 'B', 'P' }; + private static final byte[] VP8_ = new byte[]{ 'V', 'P', '8', ' ' }; + private static final byte[] VP8X = new byte[]{ 'V', 'P', '8', 'X' }; + + public WebPImageReaderSpi() { + super( + "Luciad", + "1.0", + new String[]{ "WebP", "webp" }, + new String[]{ "webp" }, + new String[]{ "image/webp" }, + WebPReader.class.getName(), + new Class[] { ImageInputStream.class }, + new String[]{ WebPImageWriterSpi.class.getName() }, + false, + null, + null, + null, + null, + false, + null, + null, + null, + null + ); + } + + @Override + public ImageReader createReaderInstance( Object extension ) throws IOException { + return new WebPReader( this ); + } + + @Override + public boolean canDecodeInput( Object source ) throws IOException { + if ( !( source instanceof ImageInputStream ) ) { + return false; + } + + ImageInputStream stream = ( ImageInputStream ) source; + byte[] b = new byte[ 4 ]; + ByteOrder oldByteOrder = stream.getByteOrder(); + stream.mark(); + stream.setByteOrder( ByteOrder.LITTLE_ENDIAN ); + + try { + stream.readFully( b ); + if ( !Arrays.equals( b, RIFF ) ) { + return false; + } + long chunkLength = stream.readUnsignedInt(); + long streamLength = stream.length(); + if ( streamLength != -1 && streamLength != chunkLength + 8 ) { + return false; + } + stream.readFully( b ); + if ( !Arrays.equals( b, WEBP ) ) { + return false; + } + + stream.readFully( b ); + if ( !Arrays.equals( b, VP8_ ) && !Arrays.equals( b, VP8X ) ) { + return false; + } + } finally { + stream.setByteOrder( oldByteOrder ); + stream.reset(); + } + + return true; + } + + @Override + public String getDescription( Locale locale ) { + return "WebP Reader"; + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPImageWriterSpi.java b/src/main/java/com/luciad/imageio/webp/WebPImageWriterSpi.java new file mode 100644 index 0000000..78e92f4 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPImageWriterSpi.java @@ -0,0 +1,110 @@ +/* + * 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 javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.io.IOException; +import java.util.Locale; + +/** + * + */ +public class WebPImageWriterSpi extends ImageWriterSpi { + public WebPImageWriterSpi() { + super( + "Luciad", + "1.0", + new String[]{ "WebP", "webp" }, + new String[]{ "webp" }, + new String[]{ "image/webp" }, + WebPReader.class.getName(), + new Class[]{ ImageOutputStream.class }, + new String[]{ WebPImageReaderSpi.class.getName() }, + false, + null, + null, + null, + null, + false, + null, + null, + null, + null + ); + } + + @Override + public boolean canEncodeImage( ImageTypeSpecifier type ) { + ColorModel colorModel = type.getColorModel(); + SampleModel sampleModel = type.getSampleModel(); + int transferType = sampleModel.getTransferType(); + + if ( colorModel instanceof ComponentColorModel ) { + if ( !( sampleModel instanceof ComponentSampleModel ) ) { + return false; + } + + if ( transferType != DataBuffer.TYPE_BYTE && transferType != DataBuffer.TYPE_INT ) { + return false; + } + } + else if ( colorModel instanceof DirectColorModel ) { + if ( !( sampleModel instanceof SinglePixelPackedSampleModel ) ) { + return false; + } + + if ( transferType != DataBuffer.TYPE_INT ) { + return false; + } + } + + ColorSpace colorSpace = colorModel.getColorSpace(); + if ( !( colorSpace.isCS_sRGB() ) ) { + return false; + } + + int[] sampleSize = sampleModel.getSampleSize(); + for ( int i = 0; i < sampleSize.length; i++ ) { + if ( sampleSize[ i ] > 8 ) { + return false; + } + } + + + return true; + } + + @Override + public ImageWriter createWriterInstance( Object extension ) throws IOException { + return new WebPWriter( this ); + } + + @Override + public String getDescription( Locale locale ) { + return "WebP Writer"; + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPReadParam.java b/src/main/java/com/luciad/imageio/webp/WebPReadParam.java new file mode 100644 index 0000000..2db6712 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPReadParam.java @@ -0,0 +1,200 @@ +/* + * 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 javax.imageio.ImageReadParam; + +public final class WebPReadParam extends ImageReadParam { + static { + WebP.loadNativeLibrary(); + } + + long fPointer; + + public WebPReadParam() { + fPointer = createDecoderOptions(); + if ( fPointer == 0 ) { + throw new OutOfMemoryError(); + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + deleteDecoderOptions( fPointer ); + fPointer = 0L; + } + + public int getCropHeight() { + return getCropHeight( fPointer ); + } + + public void setCropHeight( int aCropHeight ) { + setCropHeight( fPointer, aCropHeight ); + } + + public int getCropLeft() { + return getCropLeft( fPointer ); + } + + public void setCropLeft( int aCropLeft ) { + setCropLeft( fPointer, aCropLeft ); + } + + public int getCropTop() { + return getCropTop( fPointer ); + } + + public void setCropTop( int aCropTop ) { + setCropTop( fPointer, aCropTop ); + } + + public int getCropWidth() { + return getCropWidth( fPointer ); + } + + public void setCropWidth( int aCropWidth ) { + setCropWidth( fPointer, aCropWidth ); + } + + public boolean isForceRotation() { + return isForceRotation( fPointer ); + } + + public void setForceRotation( boolean aForceRotation ) { + setForceRotation( fPointer, aForceRotation ); + } + + public boolean isEnhancement() { + return !isNoEnhancement( fPointer ); + } + + public void setEnhancement( boolean aEnhancement ) { + setNoEnhancement( fPointer, !aEnhancement ); + } + + public boolean isFancyUpsampling() { + return !isNoFancyUpsampling( fPointer ); + } + + public void setFancyUpsampling( boolean aFancyUpsampling ) { + setNoFancyUpsampling( fPointer, !aFancyUpsampling ); + } + + public int getScaledHeight() { + return getScaledHeight( fPointer ); + } + + public void setScaledHeight( int aScaledHeight ) { + setScaledHeight( fPointer, aScaledHeight ); + } + + public int getScaledWidth() { + return getScaledWidth( fPointer ); + } + + public void setScaledWidth( int aScaledWidth ) { + setScaledWidth( fPointer, aScaledWidth ); + } + + public boolean isUseCropping() { + return isUseCropping( fPointer ); + } + + public void setUseCropping( boolean aUseCropping ) { + setUseCropping( fPointer, aUseCropping ); + } + + public boolean isUseScaling() { + return isUseScaling( fPointer ); + } + + public void setUseScaling( boolean aUseScaling ) { + setUseScaling( fPointer, aUseScaling ); + } + + public boolean isUseThreads() { + return isUseThreads( fPointer ); + } + + public void setUseThreads( boolean aUseThreads ) { + setUseThreads( fPointer, aUseThreads ); + } + + public boolean isBypassFiltering() { + return isBypassFiltering( fPointer ); + } + + public void setBypassFiltering( boolean aBypassFiltering ) { + setBypassFiltering( fPointer, aBypassFiltering ); + } + + private static native long createDecoderOptions(); + + private static native void deleteDecoderOptions( long aPointer ); + + private static native int getCropHeight( long aPointer ); + + private static native void setCropHeight( long aPointer, int aCropHeight ); + + private static native int getCropLeft( long aPointer ); + + private static native void setCropLeft( long aPointer, int aCropLeft ); + + private static native int getCropTop( long aPointer ); + + private static native void setCropTop( long aPointer, int aCropTop ); + + private static native int getCropWidth( long aPointer ); + + private static native void setCropWidth( long aPointer, int aCropWidth ); + + private static native boolean isForceRotation( long aPointer ); + + private static native void setForceRotation( long aPointer, boolean aForceRotation ); + + private static native boolean isNoEnhancement( long aPointer ); + + private static native void setNoEnhancement( long aPointer, boolean aNoEnhancement ); + + private static native boolean isNoFancyUpsampling( long aPointer ); + + private static native void setNoFancyUpsampling( long aPointer, boolean aFancyUpsampling ); + + private static native int getScaledHeight( long aPointer ); + + private static native void setScaledHeight( long aPointer, int aScaledHeight ); + + private static native int getScaledWidth( long aPointer ); + + private static native void setScaledWidth( long aPointer, int aScaledWidth ); + + private static native boolean isUseCropping( long aPointer ); + + private static native void setUseCropping( long aPointer, boolean aUseCropping ); + + private static native boolean isUseScaling( long aPointer ); + + private static native void setUseScaling( long aPointer, boolean aUseScaling ); + + private static native boolean isUseThreads( long aPointer ); + + private static native void setUseThreads( long aPointer, boolean aUseThreads ); + + private static native boolean isBypassFiltering( long aPointer ); + + private static native void setBypassFiltering( long aPointer, boolean aBypassFiltering ); +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPReader.java b/src/main/java/com/luciad/imageio/webp/WebPReader.java new file mode 100644 index 0000000..6a83d28 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPReader.java @@ -0,0 +1,151 @@ +/* + * 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 javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.Iterator; + +class WebPReader extends ImageReader { + private byte[] fData; + private int fWidth; + private int fHeight; + + WebPReader( ImageReaderSpi originatingProvider ) { + super( originatingProvider ); + } + + @Override + public void setInput( Object input, boolean seekForwardOnly, boolean ignoreMetadata ) { + super.setInput( input, seekForwardOnly, ignoreMetadata ); + fData = null; + fWidth = -1; + fHeight = -1; + } + + @Override + public int getNumImages( boolean allowSearch ) throws IOException { + return 1; + } + + private void readHeader() throws IOException { + if ( fWidth != -1 && fHeight != -1 ) { + return; + } + + readData(); + int[] info = WebP.getInfo( fData, 0, fData.length ); + fWidth = info[ 0 ]; + fHeight = info[ 1 ]; + } + + private void readData() throws IOException { + if ( fData != null ) { + return; + } + + ImageInputStream input = ( ImageInputStream ) getInput(); + long length = input.length(); + if ( length > Integer.MAX_VALUE ) { + throw new IOException( "Cannot read image of size " + length ); + } + + if ( input.getStreamPosition() != 0L ) { + if ( isSeekForwardOnly() ) { + throw new IOException(); + } + else { + input.seek( 0 ); + } + } + + byte[] data; + if ( length > 0 ) { + data = new byte[ ( int ) length ]; + input.readFully( data ); + } + else { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[ 4096 ]; + int bytesRead; + while ( ( bytesRead = input.read( buffer ) ) != -1 ) { + out.write( buffer, 0, bytesRead ); + } + out.close(); + data = out.toByteArray(); + } + fData = data; + } + + private void checkIndex( int imageIndex ) { + if ( imageIndex != 0 ) { + throw new IndexOutOfBoundsException( "Invalid image index: " + imageIndex ); + } + } + + @Override + public int getWidth( int imageIndex ) throws IOException { + checkIndex( imageIndex ); + readHeader(); + return fWidth; + } + + @Override + public int getHeight( int imageIndex ) throws IOException { + checkIndex( imageIndex ); + readHeader(); + return fHeight; + } + + @Override + public IIOMetadata getStreamMetadata() throws IOException { + return null; + } + + @Override + public IIOMetadata getImageMetadata( int imageIndex ) throws IOException { + return null; + } + + @Override + public Iterator getImageTypes( int imageIndex ) throws IOException { + return Collections.singletonList( + ImageTypeSpecifier.createFromBufferedImageType( BufferedImage.TYPE_INT_ARGB ) + ).iterator(); + } + + @Override + public ImageReadParam getDefaultReadParam() { + return new WebPReadParam(); + } + + @Override + public BufferedImage read( int imageIndex, ImageReadParam param ) throws IOException { + checkIndex( imageIndex ); + readData(); + readHeader(); + WebPReadParam options = param != null ? (WebPReadParam) param : new WebPReadParam(); + return WebP.decode( options, fData, 0, fData.length ); + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPWriteParam.java b/src/main/java/com/luciad/imageio/webp/WebPWriteParam.java new file mode 100644 index 0000000..2491e95 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPWriteParam.java @@ -0,0 +1,337 @@ +/* + * 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 javax.imageio.ImageWriteParam; +import java.util.Locale; + +public class WebPWriteParam extends ImageWriteParam { + static { + WebP.loadNativeLibrary(); + } + + long fPointer; + private final int defaultLossless; + + public WebPWriteParam( Locale aLocale ) { + super( aLocale ); + fPointer = createConfig(); + if ( fPointer == 0 ) { + throw new OutOfMemoryError(); + } + defaultLossless = getLossless( fPointer ); + canWriteCompressed = true; + compressionTypes = new String[]{ + "Lossy", + "Lossless" + }; + compressionType = compressionTypes[defaultLossless]; + compressionQuality = getQuality( fPointer ) / 100f; + compressionMode = MODE_EXPLICIT; + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + deleteConfig( fPointer ); + fPointer = 0L; + } + + private static native long createConfig(); + + private static native void deleteConfig( long aPointer ); + + long getPointer() { + return fPointer; + } + + @Override + public float getCompressionQuality() { + return super.getCompressionQuality(); + } + + @Override + public void setCompressionQuality( float quality ) { + super.setCompressionQuality( quality ); + setQuality( fPointer, quality * 100f ); + } + + @Override + public void setCompressionType( String compressionType ) { + super.setCompressionType( compressionType ); + for ( int i = 0; i < compressionTypes.length; i++ ) { + if ( compressionTypes[i].equals( compressionType ) ) { + setLossless( fPointer, i ); + break; + } + } + + } + + @Override + public void unsetCompression() { + super.unsetCompression(); + setLossless( fPointer, defaultLossless ); + } + + public int getTargetSize() { + return getTargetSize( fPointer ); + } + + public void setTargetSize( int aTargetSize ) { + setTargetSize( fPointer, aTargetSize ); + } + + public float getTargetPSNR() { + return getTargetPSNR( fPointer ); + } + + public void setTargetPSNR( float aTargetPSNR ) { + setTargetPSNR( fPointer, aTargetPSNR ); + } + + public int getMethod() { + return getMethod( fPointer ); + } + + public void setMethod( int aMethod ) { + setMethod( fPointer, aMethod ); + } + + public int getSegments() { + return getSegments( fPointer ); + } + + public void setSegments( int aSegments ) { + setSegments( fPointer, aSegments ); + } + + public int getSnsStrength() { + return getSnsStrength( fPointer ); + } + + public void setSnsStrength( int aSnsStrength ) { + setSnsStrength( fPointer, aSnsStrength ); + } + + public int getFilterStrength() { + return getFilterStrength( fPointer ); + } + + public void setFilterStrength( int aFilterStrength ) { + setFilterStrength( fPointer, aFilterStrength ); + } + + public int getFilterSharpness() { + return getFilterSharpness( fPointer ); + } + + public void setFilterSharpness( int aFilterSharpness ) { + setFilterSharpness( fPointer, aFilterSharpness ); + } + + public int getFilterType() { + return getFilterType( fPointer ); + } + + public void setFilterType( int aFilterType ) { + setFilterType( fPointer, aFilterType ); + } + + public boolean isAutoAdjustFilterStrength() { + return getAutofilter( fPointer ) != 0; + } + + public void setAutoAdjustFilterStrength( boolean aAutofilter ) { + setAutofilter( fPointer, aAutofilter ? 1 : 0 ); + } + + public int getEntropyAnalysisPassCount() { + return getPass( fPointer ); + } + + public void setEntropyAnalysisPassCount( int aPass ) { + setPass( fPointer, aPass ); + } + + public boolean isShowCompressed() { + return getShowCompressed( fPointer ) != 0; + } + + public void setShowCompressed( boolean aShowCompressed ) { + setShowCompressed( fPointer, aShowCompressed ? 1 : 0 ); + } + + public int getPreprocessing() { + return getPreprocessing( fPointer ); + } + + public void setPreprocessing( int aPreprocessing ) { + setPreprocessing( fPointer, aPreprocessing ); + } + + public int getPartitions() { + return getPartitions( fPointer ); + } + + public void setPartitions( int aPartitions ) { + setPartitions( fPointer, aPartitions ); + } + + public int getPartitionLimit() { + return getPartitionLimit( fPointer ); + } + + public void setPartitionLimit( int aPartitionLimit ) { + setPartitionLimit( fPointer, aPartitionLimit ); + } + + public int getAlphaCompression() { + return getAlphaCompression( fPointer ); + } + + public void setAlphaCompression( int aAlphaCompression ) { + setAlphaCompression( fPointer, aAlphaCompression ); + } + + public int getAlphaFiltering() { + return getAlphaFiltering( fPointer ); + } + + public void setAlphaFiltering( int aAlphaFiltering ) { + setAlphaFiltering( fPointer, aAlphaFiltering ); + } + + public int getAlphaQuality() { + return getAlphaQuality( fPointer ); + } + + public void setAlphaQuality( int aAlphaQuality ) { + setAlphaQuality( fPointer, aAlphaQuality ); + } + + public boolean isEmulateJpegSize() { + return getEmulateJpegSize( fPointer ) != 0; + } + + public void setEmulateJpegSize( boolean aEmulateJpegSize ) { + setEmulateJpegSize( fPointer, aEmulateJpegSize ? 1 : 0 ); + } + + public int getThreadLevel() { + return getThreadLevel( fPointer ); + } + + public void setThreadLevel( int aThreadLevel ) { + setThreadLevel( fPointer, aThreadLevel ); + } + + public boolean isReduceMemoryUsage() { + return getLowMemory( fPointer ) != 0; + } + + public void setReduceMemoryUsage( boolean aLowMemory ) { + setLowMemory( fPointer, aLowMemory ? 1 : 0 ); + } + + private static native float getQuality( long aPointer ); + + private static native void setQuality( long aPointer, float aQuality ); + + private static native int getTargetSize( long aPointer ); + + private static native void setTargetSize( long aPointer, int aTargetSize ); + + private static native float getTargetPSNR( long aPointer ); + + private static native void setTargetPSNR( long aPointer, float aTargetPSNR ); + + private static native int getMethod( long aPointer ); + + private static native void setMethod( long aPointer, int aMethod ); + + private static native int getSegments( long aPointer ); + + private static native void setSegments( long aPointer, int aSegments ); + + private static native int getSnsStrength( long aPointer ); + + private static native void setSnsStrength( long aPointer, int aSnsStrength ); + + private static native int getFilterStrength( long aPointer ); + + private static native void setFilterStrength( long aPointer, int aFilterStrength ); + + private static native int getFilterSharpness( long aPointer ); + + private static native void setFilterSharpness( long aPointer, int aFilterSharpness ); + + private static native int getFilterType( long aPointer ); + + private static native void setFilterType( long aPointer, int aFilterType ); + + private static native int getAutofilter( long aPointer ); + + private static native void setAutofilter( long aPointer, int aAutofilter ); + + private static native int getPass( long aPointer ); + + private static native void setPass( long aPointer, int aPass ); + + private static native int getShowCompressed( long aPointer ); + + private static native void setShowCompressed( long aPointer, int aShowCompressed ); + + private static native int getPreprocessing( long aPointer ); + + private static native void setPreprocessing( long aPointer, int aPreprocessing ); + + private static native int getPartitions( long aPointer ); + + private static native void setPartitions( long aPointer, int aPartitions ); + + private static native int getPartitionLimit( long aPointer ); + + private static native void setPartitionLimit( long aPointer, int aPartitionLimit ); + + private static native int getAlphaCompression( long aPointer ); + + private static native void setAlphaCompression( long aPointer, int aAlphaCompression ); + + private static native int getAlphaFiltering( long aPointer ); + + private static native void setAlphaFiltering( long aPointer, int aAlphaFiltering ); + + private static native int getAlphaQuality( long aPointer ); + + private static native void setAlphaQuality( long aPointer, int aAlphaQuality ); + + private static native int getLossless( long aPointer ); + + private static native void setLossless( long aPointer, int aLossless ); + + private static native int getEmulateJpegSize( long aPointer ); + + private static native void setEmulateJpegSize( long aPointer, int aEmulateJpegSize ); + + private static native int getThreadLevel( long aPointer ); + + private static native void setThreadLevel( long aPointer, int aThreadLevel ); + + private static native int getLowMemory( long aPointer ); + + private static native void setLowMemory( long aPointer, int aLowMemory ); +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPWriter.java b/src/main/java/com/luciad/imageio/webp/WebPWriter.java new file mode 100644 index 0000000..795fb4d --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPWriter.java @@ -0,0 +1,72 @@ +/* + * 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 javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import java.awt.image.RenderedImage; +import java.io.IOException; + +class WebPWriter extends ImageWriter { + WebPWriter( ImageWriterSpi originatingProvider ) { + super( originatingProvider ); + } + + @Override + public ImageWriteParam getDefaultWriteParam() { + return new WebPWriteParam( getLocale() ); + } + + @Override + public IIOMetadata convertImageMetadata( IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param ) { + return null; + } + + @Override + public IIOMetadata convertStreamMetadata( IIOMetadata inData, ImageWriteParam param ) { + return null; + } + + @Override + public IIOMetadata getDefaultImageMetadata( ImageTypeSpecifier imageType, ImageWriteParam param ) { + return null; + } + + @Override + public IIOMetadata getDefaultStreamMetadata( ImageWriteParam param ) { + return null; + } + + @Override + public void write( IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param ) throws IOException { + if ( param == null ) { + param = getDefaultWriteParam(); + } + + WebPWriteParam writeParam = (WebPWriteParam) param; + + ImageOutputStream output = ( ImageOutputStream ) getOutput(); + RenderedImage ri = image.getRenderedImage(); + + byte[] encodedData = WebP.encode(writeParam, ri); + output.write( encodedData ); + } +} diff --git a/src/main/java/example/DecodingTest.java b/src/main/java/example/DecodingTest.java new file mode 100644 index 0000000..82b2466 --- /dev/null +++ b/src/main/java/example/DecodingTest.java @@ -0,0 +1,38 @@ +package example; + +import com.luciad.imageio.webp.WebPReadParam; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.FileImageInputStream; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class DecodingTest { + + public static void main(String args[]) throws IOException { + String inputWebpPath = "test_pic/test.webp"; + String outputJpgPath = "test_pic/test_.jpg"; + String outputJpegPath = "test_pic/test_.jpeg"; + String outputPngPath = "test_pic/test_.png"; + + // Obtain a WebP ImageReader instance + ImageReader reader = ImageIO.getImageReadersByMIMEType("image/webp").next(); + + // Configure decoding parameters + WebPReadParam readParam = new WebPReadParam(); + readParam.setBypassFiltering(true); + + // Configure the input on the ImageReader + reader.setInput(new FileImageInputStream(new File(inputWebpPath))); + + // Decode the image + BufferedImage image = reader.read(0, readParam); + + ImageIO.write(image, "png", new File(outputPngPath)); + ImageIO.write(image, "jpg", new File(outputJpgPath)); + ImageIO.write(image, "jpeg", new File(outputJpegPath)); + + } +} diff --git a/src/main/java/example/EncodingTest.java b/src/main/java/example/EncodingTest.java new file mode 100644 index 0000000..facad6d --- /dev/null +++ b/src/main/java/example/EncodingTest.java @@ -0,0 +1,36 @@ +package example; + +import com.luciad.imageio.webp.WebPWriteParam; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriter; +import javax.imageio.stream.FileImageOutputStream; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class EncodingTest { + + public static void main(String args[]) throws IOException { + String inputPngPath = "test_pic/test.png"; + String inputJpgPath = "test_pic/test.jpg"; + String outputWebpPath = "test_pic/test_.webp"; + + // Obtain an image to encode from somewhere + BufferedImage image = ImageIO.read(new File(inputJpgPath)); + + // Obtain a WebP ImageWriter instance + ImageWriter writer = ImageIO.getImageWritersByMIMEType("image/webp").next(); + + // Configure encoding parameters + WebPWriteParam writeParam = new WebPWriteParam(writer.getLocale()); + writeParam.setCompressionMode(WebPWriteParam.MODE_DEFAULT); + + // Configure the output on the ImageWriter + writer.setOutput(new FileImageOutputStream(new File(outputWebpPath))); + + // Encode + writer.write(null, new IIOImage(image, null, null), writeParam); + } +} diff --git a/src/main/resources/META-INF/lib/linux_32/libwebp-imageio.so b/src/main/resources/META-INF/lib/linux_32/libwebp-imageio.so new file mode 100755 index 0000000..6b196f5 Binary files /dev/null and b/src/main/resources/META-INF/lib/linux_32/libwebp-imageio.so differ diff --git a/src/main/resources/META-INF/lib/linux_64/libwebp-imageio.so b/src/main/resources/META-INF/lib/linux_64/libwebp-imageio.so new file mode 100755 index 0000000..cc668d1 Binary files /dev/null and b/src/main/resources/META-INF/lib/linux_64/libwebp-imageio.so differ diff --git a/src/main/resources/META-INF/lib/osx_32/libwebp-imageio.dylib b/src/main/resources/META-INF/lib/osx_32/libwebp-imageio.dylib new file mode 100755 index 0000000..75b913d Binary files /dev/null and b/src/main/resources/META-INF/lib/osx_32/libwebp-imageio.dylib differ diff --git a/src/main/resources/META-INF/lib/osx_64/libwebp-imageio.dylib b/src/main/resources/META-INF/lib/osx_64/libwebp-imageio.dylib new file mode 100755 index 0000000..75b913d Binary files /dev/null and b/src/main/resources/META-INF/lib/osx_64/libwebp-imageio.dylib differ diff --git a/src/main/resources/META-INF/lib/windows_32/webp-imageio.dll b/src/main/resources/META-INF/lib/windows_32/webp-imageio.dll new file mode 100755 index 0000000..636b6a8 Binary files /dev/null and b/src/main/resources/META-INF/lib/windows_32/webp-imageio.dll differ diff --git a/src/main/resources/META-INF/lib/windows_64/webp-imageio.dll b/src/main/resources/META-INF/lib/windows_64/webp-imageio.dll new file mode 100755 index 0000000..1944b81 Binary files /dev/null and b/src/main/resources/META-INF/lib/windows_64/webp-imageio.dll differ diff --git a/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi new file mode 100644 index 0000000..ae274b7 --- /dev/null +++ b/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi @@ -0,0 +1 @@ +com.luciad.imageio.webp.WebPImageReaderSpi \ No newline at end of file diff --git a/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi new file mode 100644 index 0000000..274d4d4 --- /dev/null +++ b/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi @@ -0,0 +1 @@ +com.luciad.imageio.webp.WebPImageWriterSpi \ No newline at end of file diff --git a/test_pic/test.jpg b/test_pic/test.jpg new file mode 100644 index 0000000..23204bd Binary files /dev/null and b/test_pic/test.jpg differ diff --git a/test_pic/test.png b/test_pic/test.png new file mode 100644 index 0000000..dd082c4 Binary files /dev/null and b/test_pic/test.png differ diff --git a/test_pic/test.webp b/test_pic/test.webp new file mode 100644 index 0000000..0da983e Binary files /dev/null and b/test_pic/test.webp differ