IDEA-CR-63083: move webp from android to the platform. Create a new plugin, include it in IDEA and WS WEB-36384

(cherry picked from commit faae11591a4a39d61154d93a30eaaf824bfb64d5)

GitOrigin-RevId: f849c100b551d4c3d06852ba31a9d585891b9948
This commit is contained in:
Andrey Starovoyt
2020-06-02 20:13:14 +03:00
committed by intellij-monorepo-bot
parent f95d4aef98
commit c53f94d975
21 changed files with 892 additions and 4 deletions
+1
View File
@@ -810,6 +810,7 @@
<module fileurl="file://$PROJECT_DIR$/plugins/hg4idea/intellij.vcs.hg.iml" filepath="$PROJECT_DIR$/plugins/hg4idea/intellij.vcs.hg.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/svn4idea/intellij.vcs.svn.iml" filepath="$PROJECT_DIR$/plugins/svn4idea/intellij.vcs.svn.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/svn4idea/intellij.vcs.svn.tests.iml" filepath="$PROJECT_DIR$/plugins/svn4idea/intellij.vcs.svn.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/webp/intellij.webp.iml" filepath="$PROJECT_DIR$/plugins/webp/intellij.webp.iml" />
<module fileurl="file://$PROJECT_DIR$/xml/openapi/intellij.xml.iml" filepath="$PROJECT_DIR$/xml/openapi/intellij.xml.iml" />
<module fileurl="file://$PROJECT_DIR$/xml/xml-analysis-api/intellij.xml.analysis.iml" filepath="$PROJECT_DIR$/xml/xml-analysis-api/intellij.xml.analysis.iml" />
<module fileurl="file://$PROJECT_DIR$/xml/xml-analysis-impl/intellij.xml.analysis.impl.iml" filepath="$PROJECT_DIR$/xml/xml-analysis-impl/intellij.xml.analysis.impl.iml" />
@@ -68,6 +68,7 @@ abstract class BaseIdeaProperties extends JetBrainsProductProperties {
"intellij.vcs.changeReminder",
"intellij.filePrediction",
"intellij.markdown",
"intellij.webp",
"intellij.grazie"
]
protected static final Map<String, String> CE_CLASS_VERSIONS = [
+1
View File
@@ -139,5 +139,6 @@
<orderEntry type="module" module-name="intellij.statsCollector.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.filePrediction" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.textmate" scope="TEST" />
<orderEntry type="module" module-name="intellij.webp" scope="RUNTIME" />
</component>
</module>
@@ -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<File> libFiles = context.project.libraryCollection
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<module relativePaths="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.xml" />
<orderEntry type="module" module-name="intellij.platform.lang" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/libwebp.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
+6
View File
@@ -0,0 +1,6 @@
# managed by go/iml_to_build
java_import(
name = "libwebp",
jars = ["libwebp.jar"],
visibility = ["//visibility:public"],
)
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
filegroup(
name = "libwebp",
srcs = glob([
"mac/*.dylib",
"win/*.dll",
"linux/*.so",
]),
visibility = ["//visibility:public"],
)
+95
View File
@@ -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 `<libwebp>/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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+30
View File
@@ -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.
@@ -0,0 +1,13 @@
<idea-plugin implementation-detail="true">
<id>intellij.webp</id>
<vendor>JetBrains</vendor>
<depends>com.intellij.modules.lang</depends>
<name>Webp Support</name>
<description><![CDATA[
<p>Provides support for webp images format.</p>
]]></description>
<extensions defaultExtensionNs="com.intellij">
<fileType name="Image" extensions="webp"/>
<applicationInitializedListener implementation="com.android.tools.adtui.webp.WebpMetadata"/>
</extensions>
</idea-plugin>
@@ -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<ImageTypeSpecifier> 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;
}
}
}
@@ -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;
}
}
}
@@ -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<ImageWriter> 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();
}
}
}
}
}
@@ -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() {
}
}
@@ -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 "";
}
}