Added #1909 Export/import DefineBitsJPEG3/4s alpha channel to/from separate file

("PNG/GIF/JPEG+alpha" option in GUI, "-format image:png_gif_jpeg_alpha" for commandline)
Fixed Exporting DefineJPEG3/4 with alpha channel to PNG produced JPEG instead

Refactored bulkImport shapes and images to single file.
This commit is contained in:
Jindra Petřík
2022-12-21 21:59:01 +01:00
parent 9b708491d9
commit c72215df42
12 changed files with 339 additions and 259 deletions

View File

@@ -25,10 +25,14 @@ import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings;
import com.jpexs.decompiler.flash.helpers.BMPFile;
import com.jpexs.decompiler.flash.helpers.ImageHelper;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.HasSeparateAlphaChannel;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -36,6 +40,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
/**
*
@@ -48,7 +53,7 @@ public class ImageExporter {
if (Thread.currentThread().isInterrupted()) {
return ret;
}
if (tags.isEmpty()) {
return ret;
}
@@ -76,7 +81,7 @@ public class ImageExporter {
final ImageTag imageTag = (ImageTag) t;
ImageFormat fileFormat = imageTag.getImageFormat();
ImageFormat fileFormat = imageTag.getOriginalImageFormat();
ImageFormat originalFormat = fileFormat;
if (settings.mode == ImageExportMode.PNG) {
fileFormat = ImageFormat.PNG;
@@ -92,6 +97,7 @@ public class ImageExporter {
{
final File file = new File(outdir + File.separator + Helper.makeFileName(imageTag.getCharacterExportFileName() + "." + ImageHelper.getImageFormatString(fileFormat)));
final ImageFormat ffileFormat = fileFormat;
new RetryTask(() -> {
@@ -105,9 +111,37 @@ public class ImageExporter {
ImageHelper.write(imageTag.getImageCached().getBufferedImage(), ffileFormat, file);
}
}, handler).run();
final File alphaBinFile = new File(outdir + File.separator + Helper.makeFileName(imageTag.getCharacterExportFileName() + ".alpha.bin"));
final File alphaPngFile = new File(outdir + File.separator + Helper.makeFileName(imageTag.getCharacterExportFileName() + ".alpha.png"));
if ((imageTag instanceof HasSeparateAlphaChannel)
&& (settings.mode == ImageExportMode.PNG_GIF_JPEG_ALPHA)) {
HasSeparateAlphaChannel hsac = (HasSeparateAlphaChannel) imageTag;
if (hsac.hasAlphaChannel()) {
new RetryTask(() -> {
byte[] alphaChannel = hsac.getImageAlpha();
Dimension dim = imageTag.getImageDimension();
BufferedImage img = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB);
int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
for (int i = 0; i < pixels.length; i++) {
int a = alphaChannel[i] & 0xff;
int v = 0;
int r = v;
int g = v;
int b = v;
pixels[i] = (a << 24) | (b << 16) | (g << 8) | r;
}
ImageIO.write(img, "PNG", alphaPngFile);
}, handler).run();
}
}
ret.add(file);
}
if (Thread.currentThread().isInterrupted()) {
break;
}

View File

@@ -22,5 +22,9 @@ package com.jpexs.decompiler.flash.exporters.modes;
*/
public enum ImageExportMode {
PNG_GIF_JPEG, PNG, JPEG, BMP
PNG_GIF_JPEG,
PNG,
JPEG,
BMP,
PNG_GIF_JPEG_ALPHA
}

View File

@@ -25,13 +25,25 @@ import com.jpexs.decompiler.flash.tags.DefineBitsLossless2Tag;
import com.jpexs.decompiler.flash.tags.DefineBitsLosslessTag;
import com.jpexs.decompiler.flash.tags.DefineBitsTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Helper;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
/**
*
@@ -159,4 +171,124 @@ public class ImageImporter extends TagImporter {
return res;
}
public int bulkImport(File imagesDir, SWF swf, boolean printOut) {
int count = 0;
Map<Integer, CharacterTag> characters = swf.getCharacters();
List<String> extensions = Arrays.asList("png", "jpg", "jpeg", "gif", "bmp");
List<String> alphaExtensions = Arrays.asList("png");
File allFiles[] = imagesDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
String nameLower = name.toLowerCase();
if (nameLower.endsWith(".alpha.png")) {
return false;
}
for (String ext : extensions) {
if (nameLower.endsWith("." + ext)) {
return true;
}
}
return false;
}
});
File alphaFiles[] = imagesDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
String nameLower = name.toLowerCase();
for (String ext : alphaExtensions) {
if (nameLower.endsWith(".alpha." + ext)) {
return true;
}
}
return false;
}
});
for (int characterId : characters.keySet()) {
CharacterTag tag = characters.get(characterId);
if (tag instanceof ImageTag) {
ImageTag imageTag = (ImageTag) tag;
if (!imageTag.importSupported()) {
continue;
}
List<File> existingFilesForImageTag = new ArrayList<>();
List<File> existingAlphaFilesForImageTag = new ArrayList<>();
for (File f : allFiles) {
if (f.getName().startsWith("" + characterId + ".") || f.getName().startsWith("" + characterId + "_")) {
existingFilesForImageTag.add(f);
}
}
for (File f : alphaFiles) {
if (f.getName().startsWith("" + characterId + ".") || f.getName().startsWith("" + characterId + "_")) {
existingAlphaFilesForImageTag.add(f);
}
}
existingFilesForImageTag.sort(new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
String ext1 = o1.getName().substring(o1.getName().lastIndexOf(".") + 1);
String ext2 = o2.getName().substring(o2.getName().lastIndexOf(".") + 1);
int ret = extensions.indexOf(ext1) - extensions.indexOf(ext2);
if (ret == 0) {
return o1.getName().compareTo(o2.getName());
}
return ret;
}
});
existingAlphaFilesForImageTag.sort(new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
String ext1 = o1.getName().substring(o1.getName().lastIndexOf(".") + 1);
String ext2 = o2.getName().substring(o2.getName().lastIndexOf(".") + 1);
int ret = alphaExtensions.indexOf(ext1) - alphaExtensions.indexOf(ext2);
if (ret == 0) {
return o1.getName().compareTo(o2.getName());
}
return ret;
}
});
if (!existingFilesForImageTag.isEmpty()) {
if (existingFilesForImageTag.size() > 1) {
Logger.getLogger(ImageImporter.class.getName()).log(Level.WARNING, "Multiple matching files for image tag {0} exists, {1} selected", new Object[]{characterId, existingFilesForImageTag.get(0).getName()});
}
File sourceFile = existingFilesForImageTag.get(0);
if (printOut) {
System.out.println("Importing character " + characterId + " from file " + sourceFile.getName());
}
try {
importImage(imageTag, Helper.readFile(sourceFile.getPath()));
count++;
} catch (IOException ex) {
Logger.getLogger(ImageImporter.class.getName()).log(Level.WARNING, "Cannot import image " + characterId + " from file " + sourceFile.getName(), ex);
}
}
if (!existingAlphaFilesForImageTag.isEmpty()) {
if (existingAlphaFilesForImageTag.size() > 1) {
Logger.getLogger(ImageImporter.class.getName()).log(Level.WARNING, "Multiple matching files for image alpha tag {0} exists, {1} selected", new Object[]{characterId, existingAlphaFilesForImageTag.get(0).getName()});
}
File sourceFile = existingAlphaFilesForImageTag.get(0);
if (printOut) {
System.out.println("Importing character " + characterId + " alpha from file " + sourceFile.getName());
}
try {
importImageAlpha(imageTag, Helper.readFile(sourceFile.getPath()));
} catch (IOException ex) {
Logger.getLogger(ImageImporter.class.getName()).log(Level.WARNING, "Cannot import image " + characterId + " alpha from file " + sourceFile.getName(), ex);
}
}
if (Thread.currentThread().isInterrupted()) {
break;
}
}
}
return count;
}
}

View File

@@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.importers;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.helpers.ImageHelper;
import com.jpexs.decompiler.flash.importers.svg.SvgImporter;
import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag;
import com.jpexs.decompiler.flash.tags.DefineBitsJPEG3Tag;
import com.jpexs.decompiler.flash.tags.DefineBitsJPEG4Tag;
@@ -28,15 +29,26 @@ import com.jpexs.decompiler.flash.tags.DefineShape3Tag;
import com.jpexs.decompiler.flash.tags.DefineShape4Tag;
import com.jpexs.decompiler.flash.tags.DefineShapeTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.base.ShapeTag;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
import com.jpexs.helpers.Helper;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
@@ -145,4 +157,75 @@ public class ShapeImporter {
return res;
}
public int bulkImport(File shapesDir, SWF swf, boolean noFill, boolean printOut) {
SvgImporter svgImporter = new SvgImporter();
Map<Integer, CharacterTag> characters = swf.getCharacters();
int shapeCount = 0;
List<String> extensions = Arrays.asList("svg", "png", "jpg", "jpeg", "gif", "bmp");
File allFiles[] = shapesDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
String nameLower = name.toLowerCase();
for (String ext : extensions) {
if (nameLower.endsWith("." + ext)) {
return true;
}
}
return false;
}
});
for (int characterId : characters.keySet()) {
CharacterTag tag = characters.get(characterId);
if (tag instanceof ShapeTag) {
ShapeTag shapeTag = (ShapeTag) tag;
List<File> existingFilesForShapeTag = new ArrayList<>();
for (File f : allFiles) {
if (f.getName().startsWith("" + characterId + ".") || f.getName().startsWith("" + characterId + "_")) {
existingFilesForShapeTag.add(f);
}
}
existingFilesForShapeTag.sort(new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
String ext1 = o1.getName().substring(o1.getName().lastIndexOf(".") + 1);
String ext2 = o2.getName().substring(o2.getName().lastIndexOf(".") + 1);
int ret = extensions.indexOf(ext1) - extensions.indexOf(ext2);
if (ret == 0) {
return o1.getName().compareTo(o2.getName());
}
return ret;
}
});
if (existingFilesForShapeTag.isEmpty()) {
continue;
}
if (existingFilesForShapeTag.size() > 1) {
Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Multiple matching files for shape tag {0} exists, {1} selected", new Object[]{characterId, existingFilesForShapeTag.get(0).getName()});
}
File sourceFile = existingFilesForShapeTag.get(0);
try {
if (printOut) {
System.out.println("Importing character " + characterId + " from file " + sourceFile.getName());
}
if (sourceFile.getAbsolutePath().toLowerCase().endsWith(".svg")) {
svgImporter.importSvg(shapeTag, Helper.readTextFile(sourceFile.getAbsolutePath()), !noFill);
} else {
importImage(shapeTag, Helper.readFile(sourceFile.getAbsolutePath()), 0, !noFill);
}
shapeCount++;
} catch (IOException ex) {
Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Cannot import shape " + characterId + " from file " + sourceFile.getName(), ex);
}
if (Thread.currentThread().isInterrupted()) {
break;
}
}
}
return shapeCount;
}
}

View File

@@ -22,6 +22,7 @@ import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType;
import com.jpexs.decompiler.flash.helpers.ImageHelper;
import com.jpexs.decompiler.flash.tags.base.AloneTag;
import com.jpexs.decompiler.flash.tags.base.HasSeparateAlphaChannel;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
import com.jpexs.decompiler.flash.types.BasicType;
@@ -46,7 +47,7 @@ import java.util.logging.Logger;
* @author JPEXS
*/
@SWFVersion(from = 3) //Note: GIF and PNG since version
public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag {
public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag, HasSeparateAlphaChannel {
public static final int ID = 35;
@@ -139,10 +140,12 @@ public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag {
setModified(true);
}
@Override
public byte[] getImageAlpha() throws IOException {
return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData());
}
@Override
public void setImageAlpha(byte[] data) throws IOException {
ImageFormat fmt = ImageTag.getImageFormat(imageData);
if (fmt != ImageFormat.JPEG) {
@@ -159,6 +162,11 @@ public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag {
setModified(true);
}
@Override
public boolean hasAlphaChannel() {
return bitmapAlphaData.getLength() > 0;
}
@Override
public ImageFormat getImageFormat() {
ImageFormat fmt = getOriginalImageFormat();
@@ -175,18 +183,18 @@ public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag {
@Override
public InputStream getOriginalImageData() {
if (bitmapAlphaData.getLength() == 0) { // No alpha
JpegFixer jpegFixer = new JpegFixer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
jpegFixer.fixJpeg(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()), baos);
} catch (IOException ex) {
Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, null, ex);
}
return new ByteArrayInputStream(baos.toByteArray());
//if (bitmapAlphaData.getLength() == 0) { // No alpha
JpegFixer jpegFixer = new JpegFixer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
jpegFixer.fixJpeg(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()), baos);
} catch (IOException ex) {
Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, null, ex);
}
return new ByteArrayInputStream(baos.toByteArray());
//}
return null;
//return null;
}
@Override
@@ -245,7 +253,7 @@ public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag {
} catch (IOException ex) {
Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image", ex);
}
SerializableImage img = new SerializableImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = img.getGraphics();
g.setColor(SWF.ERROR_COLOR);

View File

@@ -22,6 +22,7 @@ import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType;
import com.jpexs.decompiler.flash.helpers.ImageHelper;
import com.jpexs.decompiler.flash.tags.base.AloneTag;
import com.jpexs.decompiler.flash.tags.base.HasSeparateAlphaChannel;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
import com.jpexs.decompiler.flash.types.BasicType;
@@ -46,7 +47,7 @@ import java.util.logging.Logger;
* @author JPEXS
*/
@SWFVersion(from = 10)
public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag {
public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag, HasSeparateAlphaChannel {
public static final int ID = 90;
@@ -144,10 +145,12 @@ public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag {
setModified(true);
}
@Override
public byte[] getImageAlpha() throws IOException {
return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData());
}
@Override
public void setImageAlpha(byte[] data) throws IOException {
ImageFormat fmt = ImageTag.getImageFormat(imageData);
if (fmt != ImageFormat.JPEG) {
@@ -163,6 +166,11 @@ public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag {
clearCache();
setModified(true);
}
@Override
public boolean hasAlphaChannel() {
return bitmapAlphaData.getLength() > 0;
}
@Override
public ImageFormat getImageFormat() {

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2010-2022 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.tags.base;
import java.io.IOException;
/**
*
* @author JPEXS
*/
public interface HasSeparateAlphaChannel {
public boolean hasAlphaChannel();
public byte[] getImageAlpha() throws IOException;
public void setImageAlpha(byte[] data) throws IOException;
}