Added: #2504 WebP image format for export/import (not animated)

Limitation: It's not available on Mac x86-64 platform
This commit is contained in:
Jindra Petřík
2025-08-10 18:45:12 +02:00
parent d8dc85109b
commit 2df87d1a0a
29 changed files with 344 additions and 30 deletions

View File

@@ -124,9 +124,10 @@ It uses modified code of these libraries:
* UAB "DKD" NellyMoser ASAO codec (Decoding Nelly Moser sound format) - LGPL
* [Animated GIF Writer] (Frames to GIF export) - Creative Commons Attribution 3.0 Unported
* [Animated GIF Encoder] (Frames to GIF export)
* [gnujpdf] (PDF export) - LGPL License
* [openjdk8 Stroker] (Shapes - Miter clip drawing) - GPL License
* [Apache Flex SDK] (Decimal numbers support - Decimal128 class) - Apache Licence 2.0
* [gnujpdf] (PDF export) - LGPL
* [openjdk8 Stroker] (Shapes - Miter clip drawing) - GPL
* [Apache Flex SDK] (Decimal numbers support - Decimal128 class) - Apache License 2.0
* [webp4j] (WEBP format export/import) - MIT
And also links to these libraries:
@@ -166,3 +167,4 @@ And also links to these libraries:
[FLA Compound Document Tools]: https://github.com/jindrapetrik/flacomdoc
[TomlJ]: https://github.com/tomlj/tomlj
[ANTLR]: https://www.antlr.org/
[webp4j]: https://github.com/jindrapetrik/webp4j/tree/java8

Binary file not shown.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Mr Nanko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -242,7 +242,7 @@ auxiliary.show.customizer.message=<message>
<java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/4">
<compilation-unit>
<package-root>src</package-root>
<classpath mode="compile">../../src;lib/LZMA.jar;lib/avi.jar;lib/cmykjpeg.jar;lib/ddsreader.jar;lib/gif.jar;lib/gnujpdf.jar;lib/jlayer-1.0.2.jar;lib/jpacker.jar;lib/nellymoser.jar;lib/sfntly.jar;lib/tga.jar;lib/ttf.jar;lib/vlcj-4.7.3.jar;lib/vlcj-natives-4.7.0.jar;lib/flashdebugger.jar;lib/jna-3.5.1.jar;lib/jna-platform-3.5.1.jar;lib/gifreader.jar;lib/miterstroke.jar;lib/decimal.jar;lib/flacomdoc.jar;lib/tomlj-1.1.1.jar;lib/decimal.jar</classpath>
<classpath mode="compile">../../src;lib/LZMA.jar;lib/avi.jar;lib/cmykjpeg.jar;lib/ddsreader.jar;lib/gif.jar;lib/gnujpdf.jar;lib/jlayer-1.0.2.jar;lib/jpacker.jar;lib/nellymoser.jar;lib/sfntly.jar;lib/tga.jar;lib/ttf.jar;lib/vlcj-4.7.3.jar;lib/vlcj-natives-4.7.0.jar;lib/flashdebugger.jar;lib/jna-3.5.1.jar;lib/jna-platform-3.5.1.jar;lib/gifreader.jar;lib/miterstroke.jar;lib/decimal.jar;lib/flacomdoc.jar;lib/tomlj-1.1.1.jar;lib/decimal.jar;lib/webp4j-1.1.0.jar</classpath>
<built-to>build</built-to>
<built-to>reports</built-to>
<built-to>dist</built-to>

View File

@@ -63,6 +63,7 @@ import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import com.jpexs.helpers.SerializableImage;
import com.jpexs.helpers.utf8.Utf8Helper;
import dev.matrixlab.webp4j.WebPCodec;
import gnu.jpdf.PDFGraphics;
import gnu.jpdf.PDFJob;
import java.awt.AlphaComposite;
@@ -120,6 +121,9 @@ public class FrameExporter {
case SVG:
fem = FrameExportMode.SVG;
break;
case WEBP:
fem = FrameExportMode.WEBP;
break;
case SWF:
fem = FrameExportMode.SWF;
break;
@@ -160,6 +164,9 @@ public class FrameExporter {
case BMP:
fem = FrameExportMode.BMP;
break;
case WEBP:
fem = FrameExportMode.WEBP;
break;
case SWF:
fem = FrameExportMode.SWF;
break;
@@ -513,7 +520,9 @@ public class FrameExporter {
}
final Color fbackgroundColor = backgroundColor;
final boolean usesTransparency = settings.mode == FrameExportMode.PNG || settings.mode == FrameExportMode.GIF;
final boolean usesTransparency = settings.mode == FrameExportMode.PNG
|| settings.mode == FrameExportMode.GIF
|| settings.mode == FrameExportMode.WEBP;
final MyFrameIterator frameImages = new MyFrameIterator(tim, fframes, evl, usesTransparency, backgroundColor, settings, subFramesLength);
switch (settings.mode) {
@@ -545,6 +554,26 @@ public class FrameExporter {
}
}
break;
case WEBP:
for (File foutdir : foutdirs) {
frameImages.reset();
for (int i = 0; frameImages.hasNext(); i++) {
final int fi = i;
new RetryTask(() -> {
int fileNum = subFramesLength > 1 ? fi + 1 : (fframes.get(fi) + 1);
File f = new File(foutdir + File.separator + fileNum + ".webp");
BufferedImage img = frameImages.next();
if (img != null) {
try(FileOutputStream fos = new FileOutputStream(f)) {
fos.write(WebPCodec.encodeImage(img, 100f));
}
}
ret.add(f);
}, handler).run();
}
}
break;
case PNG:
for (File foutdir : foutdirs) {
frameImages.reset();

View File

@@ -33,6 +33,7 @@ import com.jpexs.decompiler.graph.DottedChain;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import dev.matrixlab.webp4j.WebPCodec;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
@@ -110,6 +111,11 @@ public class ImageExporter {
if (settings.mode == ImageExportMode.BMP) {
fileFormat = ImageFormat.BMP;
}
if (settings.mode == ImageExportMode.WEBP) {
fileFormat = ImageFormat.WEBP;
}
final File file = new File(outdir + File.separator + Helper.makeFileName(imageTag.getCharacterExportFileName() + "." + ImageHelper.getImageFormatString(fileFormat)));
final ImageFormat ffileFormat = fileFormat;
@@ -121,6 +127,10 @@ public class ImageExporter {
}
} else if (ffileFormat == ImageFormat.BMP) {
BMPFile.saveBitmap(imageTag.getImageCached().getBufferedImage(), file);
} else if (ffileFormat == ImageFormat.WEBP) {
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
fos.write(WebPCodec.encodeImage(imageTag.getImageCached().getBufferedImage(), 100f));
}
} else {
ImageHelper.write(imageTag.getImageCached().getBufferedImage(), ffileFormat, file);
}

View File

@@ -48,6 +48,7 @@ import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import com.jpexs.helpers.SerializableImage;
import com.jpexs.helpers.utf8.Utf8Helper;
import dev.matrixlab.webp4j.WebPCodec;
import java.awt.Graphics2D;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
@@ -158,6 +159,7 @@ public class MorphShapeExporter {
break;
case PNG_START_END:
case BMP_START_END:
case WEBP_START_END:
double unzoom = settings.zoom;
st = mst.getStartShapeTag();
rect = st.getRect();
@@ -178,6 +180,10 @@ public class MorphShapeExporter {
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true);
if (settings.mode == MorphShapeExportMode.PNG_START_END) {
ImageHelper.write(img.getBufferedImage(), ImageFormat.PNG, fileStart);
} else if (settings.mode == MorphShapeExportMode.WEBP_START_END) {
try(FileOutputStream fos = new FileOutputStream(fileStart)) {
fos.write(WebPCodec.encodeImage(img.getBufferedImage(), 100f));
}
} else {
BMPFile.saveBitmap(img.getBufferedImage(), fileStart);
}
@@ -201,6 +207,10 @@ public class MorphShapeExporter {
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true);
if (settings.mode == MorphShapeExportMode.PNG_START_END) {
ImageHelper.write(img.getBufferedImage(), ImageFormat.PNG, fileEnd);
} else if (settings.mode == MorphShapeExportMode.WEBP_START_END) {
try(FileOutputStream fos = new FileOutputStream(fileEnd)) {
fos.write(WebPCodec.encodeImage(img.getBufferedImage(), 100f));
}
} else {
BMPFile.saveBitmap(img.getBufferedImage(), fileEnd);
}

View File

@@ -46,6 +46,7 @@ import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import com.jpexs.helpers.SerializableImage;
import com.jpexs.helpers.utf8.Utf8Helper;
import dev.matrixlab.webp4j.WebPCodec;
import java.awt.Graphics2D;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
@@ -122,6 +123,7 @@ public class ShapeExporter {
break;
case PNG:
case BMP:
case WEBP:
int newWidth = (int) (rect.getWidth() * settings.zoom / SWF.unitDivisor) + 1;
int newHeight = (int) (rect.getHeight() * settings.zoom / SWF.unitDivisor) + 1;
SerializableImage img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE);
@@ -137,6 +139,10 @@ public class ShapeExporter {
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true);
if (settings.mode == ShapeExportMode.PNG) {
ImageHelper.write(img.getBufferedImage(), ImageFormat.PNG, file);
} else if (settings.mode == ShapeExportMode.WEBP) {
try(FileOutputStream fos = new FileOutputStream(file)) {
fos.write(WebPCodec.encodeImage(img.getBufferedImage(), 100f));
}
} else {
BMPFile.saveBitmap(img.getBufferedImage(), file);
}

View File

@@ -16,6 +16,8 @@
*/
package com.jpexs.decompiler.flash.exporters.modes;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
/**
* Button export mode.
*
@@ -34,8 +36,19 @@ public enum ButtonExportMode {
* BMP - Windows Bitmap
*/
BMP,
/**
* WEBP
*/
WEBP,
/**
* SWF - Shockwave Flash
*/
SWF,
SWF;
public boolean available() {
if (this == WEBP) {
return ImageFormat.WEBP.available();
}
return true;
}
}

View File

@@ -16,6 +16,8 @@
*/
package com.jpexs.decompiler.flash.exporters.modes;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
/**
* Frame export mode.
*
@@ -50,8 +52,19 @@ public enum FrameExportMode {
* BMP - Windows Bitmap
*/
BMP,
/**
* WEBP
*/
WEBP,
/**
* SWF - Shockwave Flash
*/
SWF,
SWF;
public boolean available() {
if (this == WEBP) {
return ImageFormat.WEBP.available();
}
return true;
}
}

View File

@@ -16,6 +16,8 @@
*/
package com.jpexs.decompiler.flash.exporters.modes;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
/**
* Image export mode.
*
@@ -42,5 +44,16 @@ public enum ImageExportMode {
/**
* PNG, GIF or JPEG, depending on what suits the best, plus alpha channel
*/
PNG_GIF_JPEG_ALPHA
PNG_GIF_JPEG_ALPHA,
/*
* WEBP
*/
WEBP;
public boolean available() {
if (this == WEBP) {
return ImageFormat.WEBP.available();
}
return true;
}
}

View File

@@ -16,6 +16,8 @@
*/
package com.jpexs.decompiler.flash.exporters.modes;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
/**
* Morph shape export mode.
*
@@ -44,10 +46,21 @@ public enum MorphShapeExportMode {
* BMP start and end frames - Windows Bitmap
*/
BMP_START_END,
/**
* WEBP start and end frames
*/
WEBP_START_END,
//GIF,
//AVI,
/**
* SWF - Shockwave Flash
*/
SWF,
SWF;
public boolean available() {
if (this == WEBP_START_END) {
return ImageFormat.WEBP.available();
}
return true;
}
}

View File

@@ -16,6 +16,8 @@
*/
package com.jpexs.decompiler.flash.exporters.modes;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
/**
* Shape export mode.
*
@@ -38,8 +40,19 @@ public enum ShapeExportMode {
* BMP - Windows Bitmap
*/
BMP,
/**
* WEBP
*/
WEBP,
/**
* SWF - Shockwave Flash
*/
SWF,
SWF;
public boolean available() {
if (this == WEBP) {
return ImageFormat.WEBP.available();
}
return true;
}
}

View File

@@ -16,6 +16,8 @@
*/
package com.jpexs.decompiler.flash.exporters.modes;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
/**
* Sprite export mode.
*
@@ -50,8 +52,19 @@ public enum SpriteExportMode {
* BMP - Windows Bitmap
*/
BMP,
/**
* WEBP
*/
WEBP,
/**
* SWF - Shockwave Flash
*/
SWF,
SWF;
public boolean available() {
if (this == WEBP) {
return ImageFormat.WEBP.available();
}
return true;
}
}

View File

@@ -60,6 +60,8 @@ public class MorphShapeExportSettings {
return ".png";
case BMP_START_END:
return ".bmp";
case WEBP_START_END:
return ".webp";
case SVG:
case SVG_START_END:
return ".svg";

View File

@@ -64,6 +64,8 @@ public class ShapeExportSettings {
return ".bmp";
case CANVAS:
return ".html";
case WEBP:
return ".webp";
case SWF:
return ".swf";
default:

View File

@@ -226,6 +226,8 @@ public class ImageHelper {
return "png";
case BMP:
return "bmp";
case WEBP:
return "webp";
}
throw new Error("Unsupported image format: " + format);

View File

@@ -31,6 +31,7 @@ import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import dev.matrixlab.webp4j.WebPCodec;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -77,6 +78,20 @@ public class ImageImporter extends TagImporter {
ImageHelper.write(b, ImageFormat.PNG, baos);
newData = baos.toByteArray();
}
if (newData.length >= 4
&& newData[0] == 'R'
&& newData[1] == 'I'
&& newData[2] == 'F'
&& newData[3] == 'F')
{
if (!ImageFormat.WEBP.available()) {
throw new RuntimeException("WEBP format is not supported on your platform");
}
BufferedImage b = WebPCodec.decodeImage(newData);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageHelper.write(b, ImageFormat.PNG, baos);
newData = baos.toByteArray();
}
if (tagType == 0) {
if (it instanceof DefineBitsTag) {
@@ -94,7 +109,7 @@ public class ImageImporter extends TagImporter {
&& newData[3] == (byte) 0xe0) {
tagType = DefineBitsJPEG2Tag.ID;
} else {
tagType = DefineBitsLosslessTag.ID;
tagType = DefineBitsLossless2Tag.ID;
}
}

View File

@@ -38,10 +38,12 @@ import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import dev.matrixlab.webp4j.WebPCodec;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
@@ -162,6 +164,21 @@ public class ShapeImporter {
ImageHelper.write(b, ImageFormat.PNG, baos);
newData = baos.toByteArray();
}
if (newData.length >= 4
&& newData[0] == 'R'
&& newData[1] == 'I'
&& newData[2] == 'F'
&& newData[3] == 'F')
{
if (!ImageFormat.WEBP.available()) {
throw new RuntimeException("WEBP format is not supported on your platform");
}
BufferedImage b = WebPCodec.decodeImage(newData);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageHelper.write(b, ImageFormat.PNG, baos);
newData = baos.toByteArray();
}
if (tagType == 0) {
if (ImageTag.getImageFormat(newData) == ImageFormat.JPEG) {

View File

@@ -16,6 +16,9 @@
*/
package com.jpexs.decompiler.flash.tags.enums;
import dev.matrixlab.webp4j.NativeWebP;
import dev.matrixlab.webp4j.WebPCodec;
/**
* Image format.
*
@@ -42,7 +45,11 @@ public enum ImageFormat {
/**
* BMP
*/
BMP(".bmp");
BMP(".bmp"),
/**
* WEBP
*/
WEBP(".webp");
private final String extension;
@@ -57,4 +64,15 @@ public enum ImageFormat {
public String getExtension() {
return extension;
}
public boolean available() {
if (this == WEBP) {
try {
new NativeWebP();
} catch (Throwable t) {
return false;
}
}
return true;
}
}