diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3762e90b74..5151b44419 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,6 +49,7 @@ spotbugs-gradle-plugin = "com.github.spotbugs.snom:spotbugs-gradle-plugin:6.4.8" vecmath = "javax.vecmath:vecmath:1.5.2" stb-image = "org.ngengine:stb-image:2.30.4" +imagewebp = "org.ngengine:image-webp-decoder:1.3.0" [bundles] diff --git a/jme3-android/src/main/resources/com/jme3/asset/Android.cfg b/jme3-android/src/main/resources/com/jme3/asset/Android.cfg index 953f7bbc12..fffd15bb9e 100644 --- a/jme3-android/src/main/resources/com/jme3/asset/Android.cfg +++ b/jme3-android/src/main/resources/com/jme3/asset/Android.cfg @@ -3,4 +3,4 @@ INCLUDE com/jme3/asset/General.cfg # Android specific locators LOCATOR / com.jme3.asset.plugins.AndroidLocator -LOADER com.jme3.texture.plugins.AndroidBufferImageLoader : webp, heic, heif +LOADER com.jme3.texture.plugins.AndroidBufferImageLoader : heic, heif diff --git a/jme3-core/src/main/resources/com/jme3/asset/General.cfg b/jme3-core/src/main/resources/com/jme3/asset/General.cfg index 238d42dceb..a7c8664e35 100644 --- a/jme3-core/src/main/resources/com/jme3/asset/General.cfg +++ b/jme3-core/src/main/resources/com/jme3/asset/General.cfg @@ -23,3 +23,4 @@ LOADER com.jme3.scene.plugins.gltf.BinLoader : bin LOADER com.jme3.scene.plugins.gltf.GlbLoader : glb LOADER com.jme3.texture.plugins.StbImageLoader : jpg, bmp, gif, png, jpeg, tga, psd, hdr LOADER com.jme3.audio.plugins.OGGLoader : ogg +LOADER com.jme3.texture.plugins.WebpImageLoader : webp \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java index 0ba1cb10f7..9afed11229 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java @@ -150,13 +150,13 @@ public void simpleInitApp() { // Test for normalized texture coordinates in draco //loadModelFromPath("Models/gltf/unitSquare11x11_unsignedShortTexCoords-draco.glb"); - // Uses EXT_texture_webp - not supported yet - //loadModelSample("SunglassesKhronos", "draco"); + // Uses EXT_texture_webp + // loadModelSample("SunglassesKhronos", "draco"); + // loadModelSample("CarConcept", "webp"); // Probably invalid model // See https://github.com/KhronosGroup/glTF-Sample-Assets/issues/264 // loadModelSample("VirtualCity", "draco"); - probeNode.attachChild(assets.get(0)); inputManager.addMapping("autorotate", new KeyTrigger(KeyInput.KEY_SPACE)); @@ -213,10 +213,15 @@ private void loadModelSample(String name, String type) { path += "/glTF-Binary/"; ext = "glb"; break; + case "webp": + path += "/glTF-WEBP/"; + ext = "gltf"; + break; default: path += "/glTF/"; ext = "gltf"; break; + } path += name + "." + ext; loadModelFromPath(path); diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index 93b9a1b8ce..b1e436faf5 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -15,6 +15,7 @@ dependencies { implementation project(':jme3-plugins-json') implementation project(':jme3-plugins-json-gson') implementation libs.stb.image + implementation libs.imagewebp testRuntimeOnly project(':jme3-desktop') } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java index e4e504e1ef..d9c4cbf7d8 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java @@ -73,6 +73,7 @@ public class CustomContentManager { defaultExtensionLoaders.put("KHR_texture_transform", TextureTransformExtensionLoader.class); defaultExtensionLoaders.put("KHR_materials_emissive_strength", PBREmissiveStrengthExtensionLoader.class); defaultExtensionLoaders.put("KHR_draco_mesh_compression", DracoMeshCompressionExtensionLoader.class); + defaultExtensionLoaders.put("EXT_texture_webp", TextureWebpExtensionLoader.class); } /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index 0a39689bd7..69b0696df3 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -940,8 +940,15 @@ public Texture2D readTexture(JsonObject texture, boolean flip) throws IOExceptio } JsonObject textureData = textures.get(textureIndex).getAsJsonObject(); + Integer sourceIndex = getAsInteger(textureData, "source"); + + sourceIndex = this.customContentManager.readExtensionAndExtras("texture_source", textureData, sourceIndex); + assertNotNull(sourceIndex, "Texture has no source"); + + Integer samplerIndex = getAsInteger(textureData, "sampler"); + assertNotNull(sourceIndex, "Texture has no source"); texture2d = readImage(sourceIndex, flip); @@ -1708,4 +1715,4 @@ public static void registerDefaultExtrasLoader(Class loa public static void unregisterDefaultExtrasLoader() { CustomContentManager.defaultExtraLoaderClass = UserDataLoader.class; } -} \ No newline at end of file +} diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureWebpExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureWebpExtensionLoader.java new file mode 100644 index 0000000000..20753650e9 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureWebpExtensionLoader.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * 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 'jMonkeyEngine' 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 OWNER 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. + */ +package com.jme3.scene.plugins.gltf; + +import static com.jme3.scene.plugins.gltf.GltfUtils.assertNotNull; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsInteger; + +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; +import java.io.IOException; + +/** + * Handles EXT_texture_webp texture source selection. + */ +public class TextureWebpExtensionLoader implements ExtensionLoader { + @Override + public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement webpExtension, + Object input) throws IOException { + if(!parentName.equals("texture_source"))return input; + Integer webpSourceIndex = getAsInteger((JsonObject)webpExtension, "source"); + assertNotNull(webpSourceIndex, "EXT_texture_webp extension has no source"); + return webpSourceIndex; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/texture/plugins/WebpImageLoader.java b/jme3-plugins/src/main/java/com/jme3/texture/plugins/WebpImageLoader.java new file mode 100644 index 0000000000..3244a6abd1 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/texture/plugins/WebpImageLoader.java @@ -0,0 +1,56 @@ +package com.jme3.texture.plugins; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +import org.ngengine.webp.decoder.DecodedWebP; +import org.ngengine.webp.decoder.WebPDecoder; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.export.binary.ByteUtils; +import com.jme3.texture.Image; +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; + +public class WebpImageLoader implements AssetLoader{ + + @Override + public Object load(AssetInfo assetInfo) throws IOException { + try{ + AssetKey key = assetInfo.getKey(); + TextureKey textureKey = null; + if(key instanceof TextureKey){ + textureKey = (TextureKey) key; + } + boolean flip = textureKey != null && textureKey.isFlipY(); + try(InputStream is = assetInfo.openStream()) { + byte[] data = ByteUtils.getByteContent(is); + DecodedWebP decoded = WebPDecoder.decode(data, BufferUtils::createByteBuffer); + int w = decoded.width; + int h = decoded.height; + ByteBuffer rgba = decoded.rgba; + if(flip){ + ByteBuffer flipped = BufferUtils.createByteBuffer(rgba.capacity()); + for(int y = h - 1; y >= 0; y--){ + int rowStart = y * w * 4; + for(int x = 0; x < w * 4; x++){ + flipped.put(rgba.get(rowStart + x)); + } + } + flipped.flip(); + rgba = flipped; + } + Image jmeImage = new Image(Image.Format.RGBA8, w, h, rgba, ColorSpace.sRGB); + return jmeImage; + } + }catch(Exception e){ + throw new IOException("Failed to load WebP image", e); + } + + } + +}