diff --git a/.idea/modules.xml b/.idea/modules.xml
index d083fa11fd8a..95e982404989 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -810,6 +810,7 @@
+
diff --git a/build/groovy/org/jetbrains/intellij/build/BaseIdeaProperties.groovy b/build/groovy/org/jetbrains/intellij/build/BaseIdeaProperties.groovy
index a9acb7d39230..b5e08d05e9f6 100644
--- a/build/groovy/org/jetbrains/intellij/build/BaseIdeaProperties.groovy
+++ b/build/groovy/org/jetbrains/intellij/build/BaseIdeaProperties.groovy
@@ -68,6 +68,7 @@ abstract class BaseIdeaProperties extends JetBrainsProductProperties {
"intellij.vcs.changeReminder",
"intellij.filePrediction",
"intellij.markdown",
+ "intellij.webp",
"intellij.grazie"
]
protected static final Map CE_CLASS_VERSIONS = [
diff --git a/intellij.idea.community.main.iml b/intellij.idea.community.main.iml
index 3d191da8b5f2..a1a6c3769c05 100644
--- a/intellij.idea.community.main.iml
+++ b/intellij.idea.community.main.iml
@@ -139,5 +139,6 @@
+
\ No newline at end of file
diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy
index b154b4402386..a5c6fdd32396 100644
--- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy
+++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy
@@ -89,6 +89,11 @@ class CommunityRepositoryModules {
plugin("intellij.laf.macos") {
bundlingRestrictions.supportedOs = [OsFamily.MACOS]
},
+ plugin("intellij.webp"){
+ withResource("lib/libwebp/linux", "lib/libwebp/linux")
+ withResource("lib/libwebp/mac", "lib/libwebp/mac")
+ withResource("lib/libwebp/win", "lib/libwebp/win")
+ },
plugin("intellij.laf.win10") {
bundlingRestrictions.supportedOs = [OsFamily.WINDOWS]
},
@@ -360,10 +365,6 @@ class CommunityRepositoryModules {
withResourceFromModule("intellij.android.core", "lib/sampleData", "lib/sampleData")
withResourceArchive("../android/annotations", "lib/androidAnnotations.jar")
- withResourceFromModule("intellij.android.adt.ui", "lib/libwebp/linux", "lib/libwebp/linux")
- withResourceFromModule("intellij.android.adt.ui", "lib/libwebp/mac", "lib/libwebp/mac")
- withResourceFromModule("intellij.android.adt.ui", "lib/libwebp/win", "lib/libwebp/win")
-
// here go some differences from original Android Studio layout
def getSingleFile = { BuildContext context, String projectLibName ->
List libFiles = context.project.libraryCollection
diff --git a/plugins/webp/intellij.webp.iml b/plugins/webp/intellij.webp.iml
new file mode 100644
index 000000000000..7a6585c31894
--- /dev/null
+++ b/plugins/webp/intellij.webp.iml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/webp/lib/BUILD b/plugins/webp/lib/BUILD
new file mode 100644
index 000000000000..620764c10d86
--- /dev/null
+++ b/plugins/webp/lib/BUILD
@@ -0,0 +1,6 @@
+# managed by go/iml_to_build
+java_import(
+ name = "libwebp",
+ jars = ["libwebp.jar"],
+ visibility = ["//visibility:public"],
+)
diff --git a/plugins/webp/lib/libwebp.jar b/plugins/webp/lib/libwebp.jar
new file mode 100644
index 000000000000..2fc502b97210
Binary files /dev/null and b/plugins/webp/lib/libwebp.jar differ
diff --git a/plugins/webp/lib/libwebp/BUILD b/plugins/webp/lib/libwebp/BUILD
new file mode 100644
index 000000000000..59688324a804
--- /dev/null
+++ b/plugins/webp/lib/libwebp/BUILD
@@ -0,0 +1,9 @@
+filegroup(
+ name = "libwebp",
+ srcs = glob([
+ "mac/*.dylib",
+ "win/*.dll",
+ "linux/*.so",
+ ]),
+ visibility = ["//visibility:public"],
+)
diff --git a/plugins/webp/lib/libwebp/README.md b/plugins/webp/lib/libwebp/README.md
new file mode 100644
index 000000000000..06477ecc1077
--- /dev/null
+++ b/plugins/webp/lib/libwebp/README.md
@@ -0,0 +1,95 @@
+# Building webp jni library
+
+The webp support requires a native library (`libwebp_jni`) and `libwebp.jar`.
+The native libraries are added to the `tools/adt/idea/` project in
+`adt-ui/lib/libwebp/[platform]`.
+
+This describes how to build `libwebp_jni` for each of the platforms. The steps
+must be repeated for each platform - once for 32 bit and once for 64 bit
+architectures. __Make sure to do a clean build when switching architectures__:
+`git clean -dfX`.
+
+`libwebp.jar` is copied directly from `/swig/` to `adt-ui/lib/`
+
+## Windows:
+1. Install Visual Studio 2015 with the following components:
+ - Visual C++ (including all sub components)
+ - Universal Windows App Development Tools
+ - Git for Windows
+1. Open "Git Bash", clone libwebp and checkout appropriate branch:
+ ```bat
+ git clone https://chromium.googlesource.com/webm/libwebp
+ ```
+1. Open "Developer Command Prompt for VS2015"
+1. Make the webp_jni library as per the following commands:
+ ```bat
+ # For 64 bit arch (not needed for 32 bit arch)
+ cd "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC"
+ vcvarsall amd64
+
+ cd \git\clone\location\libwebp
+ # Make libwebp
+ nmake /f Makefile.vc CFG=release-static RTLIBCFG=static OBJDIR=output
+ # Make jni binding now
+ cd swig
+ # For 32 bit
+ cl /I..\src /I"%JDK%\include" ^
+ /I"%JDK%\include\win32" ^
+ libwebp_java_wrap.c /MT /LD /Fe:webp_jni.dll ^
+ ..\output\release-static\x86\lib\libwebp.lib
+ # For 64 bit
+ cl /I..\src /I"C:\Program Files (x86)\Java\jdk1.7.0_55\include" ^
+ /I"C:\Program Files (x86)\Java\jdk1.7.0_55\include\win32" ^
+ libwebp_java_wrap.c /MT /LD /Fe:webp_jni64.dll ^
+ ..\output\release-static\x64\lib\libwebp.lib
+ ```
+1. Note that unlike mac and linux, windows library is named `webp_jni` and not
+ `libwebp_jni`.
+
+## Mac/Linux
+1. Clone libwebp and checkout appropriate branch:
+ ```bash
+ git clone https://chromium.googlesource.com/webm/libwebp
+ ```
+1. Edit makefile.unix:
+ ```diff
+ --- a/makefile.unix
+ +++ b/makefile.unix
+ @@ -14,10 +14,11 @@
+ # These flags assume you have libpng, libjpeg, libtiff and libgif installed. If
+ # not, either follow the install instructions below or just comment out the next
+ # four lines.
+ -EXTRA_FLAGS= -DWEBP_HAVE_PNG -DWEBP_HAVE_JPEG -DWEBP_HAVE_TIFF
+ -DWEBP_LIBS= -lpng -lz
+ -CWEBP_LIBS= $(DWEBP_LIBS) -ljpeg -ltiff
+ -GIF_LIBS = -lgif
+ +EXTRA_FLAGS= -fPIC -m32
+ +#-DWEBP_HAVE_PNG -DWEBP_HAVE_JPEG -DWEBP_HAVE_TIFF
+ +#DWEBP_LIBS= -lpng -lz
+ +#CWEBP_LIBS= $(DWEBP_LIBS) -ljpeg -ltiff
+ +#GIF_LIBS = -lgif
+
+ ifeq ($(strip $(shell uname)), Darwin)
+ # Work around a problem linking tables marked as common symbols,
+ ```
+1. The above diff is for building for 32 bit arch. To build for 64 bit arch, do
+ not add `-m32` to `EXTRA_FLAGS`.
+1. Make the libwebp_jni library as per the following commands
+ ```bash
+ cd /git/clone/location/libwebp
+ make -f makefile.unix
+ cd swig
+ gcc -shared -fPIC -fno-strict-aliasing -O2 \
+ -m32 \
+ -I/path/to/your/jdk/include \
+ -I/path/to/your/jdk/include/[platform] \
+ -I../src \
+ -L../src \
+ libwebp_java_wrap.c \
+ -lwebp \
+ -o libwebp_jni.[ext]
+ ```
+1. When building for 64 bit, use `-o libwebp_jni64.[ext]` instead and do not
+ specify the `-m32` option.
+1. The `[ext]` for linux is `so` and for mac is `dylib`.
+1. No need to build the 32 bit variant for mac.
diff --git a/plugins/webp/lib/libwebp/linux/libwebp_jni.so b/plugins/webp/lib/libwebp/linux/libwebp_jni.so
new file mode 100755
index 000000000000..501c381cca4f
Binary files /dev/null and b/plugins/webp/lib/libwebp/linux/libwebp_jni.so differ
diff --git a/plugins/webp/lib/libwebp/linux/libwebp_jni64.so b/plugins/webp/lib/libwebp/linux/libwebp_jni64.so
new file mode 100755
index 000000000000..c33a06c73809
Binary files /dev/null and b/plugins/webp/lib/libwebp/linux/libwebp_jni64.so differ
diff --git a/plugins/webp/lib/libwebp/mac/libwebp_jni64.dylib b/plugins/webp/lib/libwebp/mac/libwebp_jni64.dylib
new file mode 100755
index 000000000000..e127464da47f
Binary files /dev/null and b/plugins/webp/lib/libwebp/mac/libwebp_jni64.dylib differ
diff --git a/plugins/webp/lib/libwebp/win/webp_jni.dll b/plugins/webp/lib/libwebp/win/webp_jni.dll
new file mode 100755
index 000000000000..98af3587ae11
Binary files /dev/null and b/plugins/webp/lib/libwebp/win/webp_jni.dll differ
diff --git a/plugins/webp/lib/libwebp/win/webp_jni64.dll b/plugins/webp/lib/libwebp/win/webp_jni64.dll
new file mode 100755
index 000000000000..7293adb7fb5e
Binary files /dev/null and b/plugins/webp/lib/libwebp/win/webp_jni64.dll differ
diff --git a/plugins/webp/lib/licenses/libwebp-NOTICE b/plugins/webp/lib/licenses/libwebp-NOTICE
new file mode 100644
index 000000000000..7a6f99547d4d
--- /dev/null
+++ b/plugins/webp/lib/licenses/libwebp-NOTICE
@@ -0,0 +1,30 @@
+Copyright (c) 2010, Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Google nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/plugins/webp/resources/META-INF/plugin.xml b/plugins/webp/resources/META-INF/plugin.xml
new file mode 100644
index 000000000000..2751cb925f67
--- /dev/null
+++ b/plugins/webp/resources/META-INF/plugin.xml
@@ -0,0 +1,13 @@
+
+ intellij.webp
+ JetBrains
+ com.intellij.modules.lang
+ Webp Support
+ Provides support for webp images format.
+ ]]>
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/webp/src/com/android/tools/adtui/webp/WebpImageReaderSpi.java b/plugins/webp/src/com/android/tools/adtui/webp/WebpImageReaderSpi.java
new file mode 100644
index 000000000000..4e95cd85e82c
--- /dev/null
+++ b/plugins/webp/src/com/android/tools/adtui/webp/WebpImageReaderSpi.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.tools.adtui.webp;
+
+
+import com.google.webp.libwebp;
+import com.intellij.openapi.diagnostic.Logger;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.Iterator;
+import java.util.Locale;
+
+/**
+ * Decoder for WebP. This needs the webp jni library loaded to function.
+ *
+ * @see IIORegistry
+ */
+public class WebpImageReaderSpi extends ImageReaderSpi {
+
+ private static final byte[] RIFF_HEADER = {'R', 'I', 'F', 'F'};
+ private static final byte[] WEBP_HEADER = {'W', 'E', 'B', 'P'};
+
+ private static final int MAX_FILE_SIZE = 0x6400000; // 100 Megs
+
+ WebpImageReaderSpi() {
+ vendorName = WebpMetadata.WEBP_VENDOR;
+ version = WebpNativeLibHelper.getDecoderVersion();
+ suffixes = WebpMetadata.WEBP_SUFFIXES;
+ names = WebpMetadata.WEBP_FORMAT_NAMES;
+ MIMETypes = WebpMetadata.WEBP_MIME_TYPES;
+ pluginClassName = WebpReader.class.getName();
+ inputTypes = new Class>[]{ImageInputStream.class};
+ }
+
+ @Override
+ public boolean canDecodeInput(@NotNull Object source) throws IOException {
+ assert source instanceof ImageInputStream;
+ ImageInputStream stream = (ImageInputStream) source;
+ long length = stream.length();
+ // The length may be -1 for files of unknown size.
+ // Accept them for now and if needed, throw an IOException later.
+ if (length > MAX_FILE_SIZE) {
+ return false;
+ }
+
+ stream.mark();
+ try {
+ byte[] header = new byte[12];
+ int bytesRead = stream.read(header, 0, 12);
+ return bytesRead == 12 &&
+ arrayEquals(header, 0, RIFF_HEADER.length, RIFF_HEADER) &&
+ arrayEquals(header, 8, WEBP_HEADER.length, WEBP_HEADER) &&
+ WebpNativeLibHelper.loadNativeLibraryIfNeeded();
+ } finally {
+ try {
+ stream.reset();
+ }
+ catch (IOException e) {
+ Logger.getInstance(WebpImageReaderSpi.class).error(e);
+ }
+ }
+ }
+
+ private static boolean arrayEquals(@NotNull byte[] a1, int offset, int len, @NotNull byte[] a2) {
+ for (int i = 0; i < len; i++) {
+ if (a1[offset + i] != a2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @NotNull
+ @Override
+ public ImageReader createReaderInstance(Object extension) throws IOException {
+ WebpNativeLibHelper.requireNativeLibrary();
+ return new WebpReader(this);
+ }
+
+ @NotNull
+ @Override
+ public String getDescription(Locale locale) {
+ return "Webp Image Decoder";
+ }
+
+ private static class WebpReader extends ImageReader {
+
+ private static final String UNABLE_TO_READ_WEBP_IMAGE = "Unable to read WebP image";
+
+ @Nullable
+ private byte[] myInputBytes;
+ private final int[] myWidthOut = new int[1];
+ private final int[] myHeightOut = new int[1];
+ private int myError;
+
+ public WebpReader(ImageReaderSpi originatingProvider) {
+ super(originatingProvider);
+ }
+
+ @Override
+ public void dispose() {
+ myInputBytes = null;
+ }
+
+ @Override
+ public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
+ super.setInput(input, seekForwardOnly, ignoreMetadata);
+ myInputBytes = null;
+ myError = 0;
+ }
+
+ @Override
+ public int getNumImages(boolean allowSearch) throws IOException {
+ return myInputBytes == null ? 0 : 1;
+ }
+
+ private void loadInfoIfNeeded() throws IOException {
+ if (input != null && myInputBytes == null) {
+ myInputBytes = readStreamFully((ImageInputStream) input);
+ myError = libwebp.WebPGetInfo(myInputBytes, myInputBytes.length, myWidthOut, myHeightOut);
+ }
+ }
+
+ @NotNull
+ private static byte[] readStreamFully(@NotNull ImageInputStream stream) throws IOException {
+ if (stream.length() != -1) {
+ byte[] bytes = new byte[(int) stream.length()]; // Integer overflow prevented by canDecode check in reader spi above.
+
+ stream.readFully(bytes);
+ return bytes;
+ }
+
+ // Unknown file size
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(0x100000); // initialize with 1 Meg to minimize reallocs.
+ final int bufferSize = 0x4000; // 16k
+ byte[] bytes = new byte[bufferSize];
+ int idx;
+ for (idx = 0; idx < MAX_FILE_SIZE / bufferSize; idx++) { // Just to make sure we don't exceed MAX_FILE_SIZE
+ int read = stream.read(bytes, 0, bufferSize);
+ buffer.write(bytes, 0, read);
+ if (read != bufferSize) {
+ break;
+ }
+ }
+ if (idx == MAX_FILE_SIZE / bufferSize) {
+ throw new IOException("webp image too large");
+ }
+ return buffer.toByteArray();
+ }
+
+ @Override
+ public int getWidth(int imageIndex) throws IOException {
+ loadInfoIfNeeded();
+ if (myError != 0) {
+ // Yes, the above check is correct - zero value means an error.
+ return myWidthOut[0];
+ }
+ throw new IOException(UNABLE_TO_READ_WEBP_IMAGE);
+ }
+
+ @Override
+ public int getHeight(int imageIndex) throws IOException {
+ loadInfoIfNeeded();
+ if (myError != 0) {
+ // Yes, the above check is correct - zero value means an error.
+ return myHeightOut[0];
+ }
+ throw new IOException(UNABLE_TO_READ_WEBP_IMAGE);
+ }
+
+ @Nullable
+ @Override
+ public Iterator getImageTypes(int imageIndex) throws IOException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public IIOMetadata getStreamMetadata() throws IOException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
+ loadInfoIfNeeded();
+ if (myError == 0) {
+ throw new IOException(UNABLE_TO_READ_WEBP_IMAGE);
+ }
+ assert myInputBytes != null;
+ byte[] argb = libwebp.WebPDecodeARGB(myInputBytes, myInputBytes.length, myWidthOut, myHeightOut);
+ @SuppressWarnings("UndesirableClassUsage")
+ BufferedImage bi = new BufferedImage(myWidthOut[0], myHeightOut[0], BufferedImage.TYPE_INT_ARGB);
+ // Copy the bytes read above to the image's data buffer.
+ final int[] a = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData();
+ IntBuffer intBuf = ByteBuffer.wrap(argb).asIntBuffer();
+ assert a.length == intBuf.remaining();
+ intBuf.get(a);
+ return bi;
+ }
+ }
+}
diff --git a/plugins/webp/src/com/android/tools/adtui/webp/WebpImageWriteParam.java b/plugins/webp/src/com/android/tools/adtui/webp/WebpImageWriteParam.java
new file mode 100644
index 000000000000..eb4c7389a242
--- /dev/null
+++ b/plugins/webp/src/com/android/tools/adtui/webp/WebpImageWriteParam.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.tools.adtui.webp;
+
+import javax.imageio.ImageWriteParam;
+import java.util.Locale;
+
+class WebpImageWriteParam extends ImageWriteParam {
+ WebpImageWriteParam(Locale locale, boolean compress, float quality) {
+ super(locale);
+ this.canWriteCompressed = compress;
+ this.compressionQuality = quality;
+ if (canWriteCompressed) {
+ compressionMode = ImageWriteParam.MODE_EXPLICIT;
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/webp/src/com/android/tools/adtui/webp/WebpImageWriterSpi.java b/plugins/webp/src/com/android/tools/adtui/webp/WebpImageWriterSpi.java
new file mode 100644
index 000000000000..59467a1a6fe3
--- /dev/null
+++ b/plugins/webp/src/com/android/tools/adtui/webp/WebpImageWriterSpi.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.tools.adtui.webp;
+
+
+import com.google.webp.libwebp;
+import org.jetbrains.annotations.NotNull;
+
+import javax.imageio.*;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.*;
+import java.awt.image.*;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteOrder;
+import java.util.Iterator;
+import java.util.Locale;
+
+/**
+ * Encoder for WebP. This needs the webp jni library loaded to function.
+ */
+public class WebpImageWriterSpi extends ImageWriterSpi {
+ WebpImageWriterSpi() {
+ vendorName = WebpMetadata.WEBP_VENDOR;
+ version = WebpNativeLibHelper.getEncoderVersion();
+ suffixes = WebpMetadata.WEBP_SUFFIXES;
+ names = WebpMetadata.WEBP_FORMAT_NAMES;
+ MIMETypes = WebpMetadata.WEBP_MIME_TYPES;
+ pluginClassName = WebpWriter.class.getName();
+ outputTypes = new Class>[]{ImageOutputStream.class};
+ }
+
+ public static void writeImage(@NotNull BufferedImage image,
+ @NotNull OutputStream outputStream,
+ boolean lossless,
+ int quality) throws IOException {
+ // Instead of
+ // ImageIO.write(image, WebpMetadata.WEBP_FORMAT_LOWER_CASE, stream);
+ // which will pass null as the parameters to the ImageWriter, call through to the ImageWriter
+ // more directly so we can set up the proper parameters:
+ ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(image);
+ Iterator iterator = ImageIO.getImageWriters(type, WebpMetadata.WEBP_FORMAT_LOWER_CASE);
+ if (iterator.hasNext()) {
+ ImageWriter writer = iterator.next();
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(outputStream)) {
+ writer.setOutput(stream);
+ try {
+ ImageWriteParam param = new WebpImageWriteParam(writer.getLocale(), !lossless, quality / 100f);
+ writer.write(null, new IIOImage(image, null, null), param);
+ }
+ finally {
+ writer.dispose();
+ stream.flush();
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean canEncodeImage(ImageTypeSpecifier type) {
+ return canWriteImage(type);
+ }
+
+ public static boolean canWriteImage(ImageTypeSpecifier type) {
+ SampleModel sm = type.getSampleModel();
+ ColorModel cm = type.getColorModel();
+ return (sm.getNumBands() >= 2 && sm.getNumBands() <= 4 || cm instanceof IndexColorModel) &&
+ sm.getSampleSize(0) <= 8 &&
+ sm.getWidth() <= 65535 &&
+ sm.getHeight() <= 65535 &&
+ (cm == null || cm.getComponentSize()[0] <= 8) &&
+ WebpNativeLibHelper.loadNativeLibraryIfNeeded();
+ }
+
+ @Override
+ public ImageWriter createWriterInstance(Object extension) throws IOException {
+ WebpNativeLibHelper.requireNativeLibrary();
+ return new WebpWriter(this);
+ }
+
+ @Override
+ public String getDescription(Locale locale) {
+ return "WebP Image Encoder";
+ }
+
+ private static class WebpWriter extends ImageWriter {
+ public WebpWriter(ImageWriterSpi originatingProvider) {
+ super(originatingProvider);
+ }
+
+ @Override
+ public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
+ return new WebpMetadata();
+ }
+
+ @Override
+ public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
+ return new WebpMetadata();
+ }
+
+ @Override
+ public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) {
+ return null;
+ }
+
+ @Override
+ public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
+ return null;
+ }
+
+ private ImageOutputStream stream = null;
+
+ @Override
+ public void setOutput(Object output) {
+ super.setOutput(output);
+ if (output != null) {
+ if (!(output instanceof ImageOutputStream)) {
+ throw new
+ IllegalArgumentException("output is not an ImageOutputStream");
+ }
+ this.stream = (ImageOutputStream)output;
+ this.stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ }
+ else {
+ this.stream = null;
+ }
+ }
+
+
+ @Override
+ public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
+ if (stream == null) {
+ throw new IllegalStateException("output == null!");
+ }
+ boolean lossless = WebpMetadata.DEFAULT_LOSSLESS;
+ float quality = WebpMetadata.DEFAULT_ENCODING_QUALITY;
+ if (param != null && param.canWriteCompressed()) {
+ lossless = false;
+ if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
+ quality = param.getCompressionQuality();
+ }
+ }
+
+ Raster srcRas = getRaster(image);
+
+ int minX = srcRas.getMinX();
+ int minY = srcRas.getMinY();
+ int width = srcRas.getWidth();
+ int height = srcRas.getHeight();
+ int maxX = minX + width;
+ int maxY = minY + height;
+
+ byte[] data = new byte[width * height * 4];
+ // TODO: This code is the inefficient fallback dealing with any color/sample model.
+ // However, we've restricted things in the canEncodeImage method so we might as well try
+ // to do it more efficiently
+ int index = 0;
+ SampleModel sampleModel = srcRas.getSampleModel();
+ int bands = sampleModel.getNumBands();
+ if (bands == 1) {
+ // Indexed image but not found in raster
+ return;
+ }
+ assert bands >= 2 && bands <= 4; // enforced in canEncodeImage
+ DataBuffer buffer = srcRas.getDataBuffer();
+ if (bands >= 3) {
+ for (int y = minY; y < maxY; y++) {
+ for (int x = minX; x < maxX; x++) {
+ for (int band = 0; band < bands; band++) {
+ int sample = sampleModel.getSample(x, y, band, buffer);
+ data[index++] = (byte)sample;
+ }
+ if (bands == 3) {
+ data[index++] = (byte)255; // implicit alpha in an RGB (not RGBA) image
+ }
+ }
+ }
+ }
+ else {
+ assert bands == 2;
+ for (int y = minY; y < maxY; y++) {
+ for (int x = minX; x < maxX; x++) {
+ byte sample = (byte)sampleModel.getSample(x, y, 0, buffer);
+ data[index++] = sample;
+ data[index++] = sample;
+ data[index++] = sample;
+ byte alpha = (byte)sampleModel.getSample(x, y, 1, buffer);
+ data[index++] = alpha;
+ }
+ }
+ }
+
+ // See https://developers.google.com/speed/webp/docs/api
+ byte[] encoded;
+ if (lossless) {
+ encoded = libwebp.WebPEncodeLosslessRGBA(data, width, height, 4 * width);
+ }
+ else {
+ encoded = libwebp.WebPEncodeRGBA(data, width, height, 4 * width, (int)(100 * quality));
+ }
+ stream.write(encoded);
+ }
+
+ @NotNull
+ private static Raster getRaster(@NotNull IIOImage image) {
+ boolean rasterOnly = image.hasRaster();
+ if (rasterOnly) {
+ return image.getRaster();
+ }
+ else {
+ RenderedImage renderedImage = image.getRenderedImage();
+ if (renderedImage instanceof BufferedImage) {
+ // Convert indexed to RGB
+ BufferedImage bufferedImage = (BufferedImage)renderedImage;
+ if (renderedImage.getColorModel() instanceof IndexColorModel) {
+ //noinspection UndesirableClassUsage
+ BufferedImage rgb = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphics = rgb.createGraphics();
+ graphics.drawImage(bufferedImage, 0, 0, null);
+ graphics.dispose();
+ return rgb.getRaster();
+ }
+ return bufferedImage.getRaster();
+ }
+ else if (renderedImage.getNumXTiles() == 1 &&
+ renderedImage.getNumYTiles() == 1) {
+ Raster raster = renderedImage.getTile(renderedImage.getMinTileX(),
+ renderedImage.getMinTileY());
+ if (raster.getWidth() != renderedImage.getWidth() ||
+ raster.getHeight() != renderedImage.getHeight()) {
+ raster = raster.createChild(raster.getMinX(),
+ raster.getMinY(),
+ renderedImage.getWidth(),
+ renderedImage.getHeight(),
+ raster.getMinX(),
+ raster.getMinY(),
+ null);
+ }
+ return raster;
+ }
+ else {
+ return renderedImage.getData();
+ }
+ }
+ }
+ }
+}
diff --git a/plugins/webp/src/com/android/tools/adtui/webp/WebpMetadata.java b/plugins/webp/src/com/android/tools/adtui/webp/WebpMetadata.java
new file mode 100644
index 000000000000..c98c62b9b9f6
--- /dev/null
+++ b/plugins/webp/src/com/android/tools/adtui/webp/WebpMetadata.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.tools.adtui.webp;
+
+import com.intellij.ide.ApplicationInitializedListener;
+import org.w3c.dom.Node;
+
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ImageWriterSpi;
+
+public class WebpMetadata extends IIOMetadata implements ApplicationInitializedListener {
+ public static final String WEBP_FORMAT_LOWER_CASE = "webp";
+ public static final String WEBP_FORMAT_UPPER_CASE = "WEBP";
+ public static final String[] WEBP_FORMAT_NAMES = new String[] {WEBP_FORMAT_UPPER_CASE, WEBP_FORMAT_LOWER_CASE};
+ public static final String EXT_WEBP = WEBP_FORMAT_LOWER_CASE;
+ public static final String[] WEBP_SUFFIXES = new String[] { EXT_WEBP};
+ public static final String[] WEBP_MIME_TYPES = new String[] {"image/webp"};
+ public static final String WEBP_VENDOR = "Google LLC";
+ public static final float DEFAULT_ENCODING_QUALITY = 0.75f;
+ public static final boolean DEFAULT_LOSSLESS = true;
+
+ @Override
+ public void componentsInitialized() {
+ ensureWebpRegistered();
+ }
+
+ /**
+ * Ensures that service providers are registered.
+ */
+ public static void ensureWebpRegistered() {
+ IIORegistry.getDefaultInstance().registerServiceProvider(new WebpImageReaderSpi(), ImageReaderSpi.class);
+ IIORegistry.getDefaultInstance().registerServiceProvider(new WebpImageWriterSpi(), ImageWriterSpi.class);
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public Node getAsTree(String formatName) {
+ return new IIOMetadataNode(nativeMetadataFormatName);
+ }
+
+ @Override
+ public void mergeTree(String formatName, Node root) throws IIOInvalidTreeException {
+ }
+
+ @Override
+ public void reset() {
+ }
+}
diff --git a/plugins/webp/src/com/android/tools/adtui/webp/WebpNativeLibHelper.java b/plugins/webp/src/com/android/tools/adtui/webp/WebpNativeLibHelper.java
new file mode 100644
index 000000000000..6e2c676c2d2e
--- /dev/null
+++ b/plugins/webp/src/com/android/tools/adtui/webp/WebpNativeLibHelper.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.tools.adtui.webp;
+
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.application.PluginPathManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.SystemInfo;
+
+import java.io.File;
+import java.io.IOException;
+
+public class WebpNativeLibHelper {
+
+ @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized") private static boolean sJniLibLoaded;
+ @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized") private static boolean sJniLibLoadAttempted;
+
+ private WebpNativeLibHelper() {
+ }
+
+ static void requireNativeLibrary() throws IOException {
+ if (!loadNativeLibraryIfNeeded()) {
+ throw new IOException("The WebP decoder library could not be loaded");
+ }
+ }
+
+ public static String getDecoderVersion() {
+ // This is the current result of calling
+ // libwebp.WebPGetEncoderVersion()
+ // but we don't want to have to load the native library just to get this constant
+ // (since it doesn't change until we change the bundled version of the library anyway).
+ //
+ // And more importantly, NOBODY looks at this version anyway.
+ return "1280";
+ }
+
+ public static String getEncoderVersion() {
+ return getDecoderVersion();
+ }
+
+ public static boolean loadNativeLibraryIfNeeded() {
+ if (!sJniLibLoadAttempted) {
+ try {
+ loadNativeLibrary();
+ }
+ catch (UnsatisfiedLinkError e) {
+ Logger.getInstance(WebpImageReaderSpi.class).warn(e);
+ }
+ }
+ return sJniLibLoaded;
+ }
+
+ private synchronized static void loadNativeLibrary() {
+ if (sJniLibLoadAttempted) {
+ // Already attempted to load, nothing to do here.
+ return;
+ }
+ try {
+ String libFileName = getLibName();
+ File pluginPath = getLibLocation();
+ File libPath = new File(pluginPath, libFileName);
+ if (!libPath.exists()) {
+ throw new UnsatisfiedLinkError(String.format("'%1$s' not found at '%2$s'", libFileName, libPath.getAbsolutePath()));
+ }
+ System.load(libPath.getAbsolutePath());
+ }
+ finally {
+ sJniLibLoadAttempted = true;
+ }
+ sJniLibLoaded = true;
+ }
+
+ /**
+ * Copied from {@link com.intellij.util.lang.UrlClassLoader}.
+ */
+ public static String getLibName() {
+ String baseName = SystemInfo.is64Bit ? "webp_jni64" : "webp_jni";
+ String fileName = System.mapLibraryName(baseName);
+ if (SystemInfo.isMac) {
+ fileName = fileName.replace(".jnilib", ".dylib");
+ }
+ return fileName;
+ }
+
+ public static File getLibLocation() {
+ // A terrible hack for dev environment.
+ String libPath = "/lib/libwebp/" + getPlatformName();
+ return new File(PluginPathManager.getPluginHomePath("webp") + libPath);
+ }
+
+ private static String getPlatformName() {
+ if (SystemInfo.isWindows) return "win";
+ else if (SystemInfo.isMac) return "mac";
+ else if (SystemInfo.isLinux) return "linux";
+ else return "";
+ }
+}