mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-05-29 10:24:38 +00:00
#882 Canvas export border size
This commit is contained in:
@@ -1,137 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2015 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.exporters;
|
||||
|
||||
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
|
||||
import com.jpexs.decompiler.flash.EventListener;
|
||||
import com.jpexs.decompiler.flash.RetryTask;
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.modes.MorphShapeExportMode;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.CanvasMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.settings.MorphShapeExportSettings;
|
||||
import com.jpexs.decompiler.flash.tags.DefineMorphShapeTag;
|
||||
import com.jpexs.decompiler.flash.tags.Tag;
|
||||
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.MorphShapeTag;
|
||||
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
|
||||
import com.jpexs.helpers.Helper;
|
||||
import com.jpexs.helpers.Path;
|
||||
import com.jpexs.helpers.utf8.Utf8Helper;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class MorphShapeExporter {
|
||||
|
||||
//TODO: implement morphshape export. How to handle 65536 frames?
|
||||
public List<File> exportMorphShapes(AbortRetryIgnoreHandler handler, final String outdir, List<Tag> tags, final MorphShapeExportSettings settings, EventListener evl) throws IOException {
|
||||
List<File> ret = new ArrayList<>();
|
||||
if (tags.isEmpty()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
File foutdir = new File(outdir);
|
||||
Path.createDirectorySafe(foutdir);
|
||||
|
||||
int count = 0;
|
||||
for (Tag t : tags) {
|
||||
if (t instanceof MorphShapeTag) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int currentIndex = 1;
|
||||
for (final Tag t : tags) {
|
||||
if (t instanceof MorphShapeTag) {
|
||||
if (evl != null) {
|
||||
evl.handleExportingEvent("morphshape", currentIndex, count, t.getName());
|
||||
}
|
||||
|
||||
int characterID = 0;
|
||||
if (t instanceof CharacterTag) {
|
||||
characterID = ((CharacterTag) t).getCharacterId();
|
||||
}
|
||||
String ext = settings.mode == MorphShapeExportMode.CANVAS ? "html" : "svg";
|
||||
|
||||
final File file = new File(outdir + File.separator + characterID + "." + ext);
|
||||
new RetryTask(() -> {
|
||||
MorphShapeTag mst = (MorphShapeTag) t;
|
||||
switch (settings.mode) {
|
||||
case SVG:
|
||||
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
|
||||
ExportRectangle rect = new ExportRectangle(mst.getRect());
|
||||
rect.xMax *= settings.zoom;
|
||||
rect.yMax *= settings.zoom;
|
||||
rect.xMin *= settings.zoom;
|
||||
rect.yMin *= settings.zoom;
|
||||
SVGExporter exporter = new SVGExporter(rect);
|
||||
mst.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, settings.zoom);
|
||||
fos.write(Utf8Helper.getBytes(exporter.getSVG()));
|
||||
}
|
||||
break;
|
||||
case CANVAS:
|
||||
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
|
||||
int deltaX = -Math.min(mst.getStartBounds().Xmin, mst.getEndBounds().Xmin);
|
||||
int deltaY = -Math.min(mst.getStartBounds().Ymin, mst.getEndBounds().Ymin);
|
||||
CanvasMorphShapeExporter cse = new CanvasMorphShapeExporter(((Tag) mst).getSwf(), mst.getShapeAtRatio(0), mst.getShapeAtRatio(DefineMorphShapeTag.MAX_RATIO), new CXFORMWITHALPHA(), SWF.unitDivisor, deltaX, deltaY);
|
||||
cse.export();
|
||||
Set<Integer> needed = new HashSet<>();
|
||||
CharacterTag ct = ((CharacterTag) mst);
|
||||
needed.add(ct.getCharacterId());
|
||||
ct.getNeededCharactersDeep(needed);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
SWF.writeLibrary(ct.getSwf(), needed, baos);
|
||||
fos.write(Utf8Helper.getBytes(cse.getHtml(new String(baos.toByteArray(), "UTF-8"))));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}, handler).run();
|
||||
ret.add(file);
|
||||
|
||||
if (evl != null) {
|
||||
evl.handleExportedEvent("morphshape", currentIndex, count, t.getName());
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.mode == MorphShapeExportMode.CANVAS) {
|
||||
File fcanvas = new File(foutdir + File.separator + "canvas.js");
|
||||
Helper.saveStream(SWF.class.getClassLoader().getResourceAsStream("com/jpexs/helpers/resource/canvas.js"), fcanvas);
|
||||
ret.add(fcanvas);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2010-2015 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.exporters;
|
||||
|
||||
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
|
||||
import com.jpexs.decompiler.flash.EventListener;
|
||||
import com.jpexs.decompiler.flash.RetryTask;
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.modes.MorphShapeExportMode;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.CanvasMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.settings.MorphShapeExportSettings;
|
||||
import com.jpexs.decompiler.flash.tags.DefineMorphShapeTag;
|
||||
import com.jpexs.decompiler.flash.tags.Tag;
|
||||
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.MorphShapeTag;
|
||||
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
|
||||
import com.jpexs.helpers.Helper;
|
||||
import com.jpexs.helpers.Path;
|
||||
import com.jpexs.helpers.utf8.Utf8Helper;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class MorphShapeExporter {
|
||||
|
||||
//TODO: implement morphshape export. How to handle 65536 frames?
|
||||
public List<File> exportMorphShapes(AbortRetryIgnoreHandler handler, final String outdir, List<Tag> tags, final MorphShapeExportSettings settings, EventListener evl) throws IOException {
|
||||
List<File> ret = new ArrayList<>();
|
||||
if (tags.isEmpty()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
File foutdir = new File(outdir);
|
||||
Path.createDirectorySafe(foutdir);
|
||||
|
||||
int count = 0;
|
||||
for (Tag t : tags) {
|
||||
if (t instanceof MorphShapeTag) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int currentIndex = 1;
|
||||
for (final Tag t : tags) {
|
||||
if (t instanceof MorphShapeTag) {
|
||||
if (evl != null) {
|
||||
evl.handleExportingEvent("morphshape", currentIndex, count, t.getName());
|
||||
}
|
||||
|
||||
int characterID = 0;
|
||||
if (t instanceof CharacterTag) {
|
||||
characterID = ((CharacterTag) t).getCharacterId();
|
||||
}
|
||||
String ext = settings.mode == MorphShapeExportMode.CANVAS ? "html" : "svg";
|
||||
|
||||
final File file = new File(outdir + File.separator + characterID + "." + ext);
|
||||
new RetryTask(() -> {
|
||||
MorphShapeTag mst = (MorphShapeTag) t;
|
||||
switch (settings.mode) {
|
||||
case SVG:
|
||||
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
|
||||
ExportRectangle rect = new ExportRectangle(mst.getRect());
|
||||
rect.xMax *= settings.zoom;
|
||||
rect.yMax *= settings.zoom;
|
||||
rect.xMin *= settings.zoom;
|
||||
rect.yMin *= settings.zoom;
|
||||
SVGExporter exporter = new SVGExporter(rect);
|
||||
mst.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, settings.zoom);
|
||||
fos.write(Utf8Helper.getBytes(exporter.getSVG()));
|
||||
}
|
||||
break;
|
||||
case CANVAS:
|
||||
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
|
||||
int deltaX = -Math.min(mst.getStartBounds().Xmin, mst.getEndBounds().Xmin);
|
||||
int deltaY = -Math.min(mst.getStartBounds().Ymin, mst.getEndBounds().Ymin);
|
||||
CanvasMorphShapeExporter cse = new CanvasMorphShapeExporter(((Tag) mst).getSwf(), mst.getShapeAtRatio(0), mst.getShapeAtRatio(DefineMorphShapeTag.MAX_RATIO), new CXFORMWITHALPHA(), SWF.unitDivisor, deltaX, deltaY);
|
||||
cse.export();
|
||||
Set<Integer> needed = new HashSet<>();
|
||||
CharacterTag ct = ((CharacterTag) mst);
|
||||
needed.add(ct.getCharacterId());
|
||||
ct.getNeededCharactersDeep(needed);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
SWF.writeLibrary(ct.getSwf(), needed, baos);
|
||||
fos.write(Utf8Helper.getBytes(cse.getHtml(new String(baos.toByteArray(), "UTF-8"), SWF.getTypePrefix(mst) + mst.getCharacterId(), mst.getRect())));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}, handler).run();
|
||||
ret.add(file);
|
||||
|
||||
if (evl != null) {
|
||||
evl.handleExportedEvent("morphshape", currentIndex, count, t.getName());
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.mode == MorphShapeExportMode.CANVAS) {
|
||||
File fcanvas = new File(foutdir + File.separator + "canvas.js");
|
||||
Helper.saveStream(SWF.class.getClassLoader().getResourceAsStream("com/jpexs/helpers/resource/canvas.js"), fcanvas);
|
||||
ret.add(fcanvas);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,167 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2015 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.exporters;
|
||||
|
||||
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
|
||||
import com.jpexs.decompiler.flash.EventListener;
|
||||
import com.jpexs.decompiler.flash.RetryTask;
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode;
|
||||
import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter;
|
||||
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.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
import com.jpexs.decompiler.flash.tags.base.ShapeTag;
|
||||
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
|
||||
import com.jpexs.decompiler.flash.types.RECT;
|
||||
import com.jpexs.decompiler.flash.types.SHAPE;
|
||||
import com.jpexs.helpers.Helper;
|
||||
import com.jpexs.helpers.Path;
|
||||
import com.jpexs.helpers.SerializableImage;
|
||||
import com.jpexs.helpers.utf8.Utf8Helper;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class ShapeExporter {
|
||||
|
||||
public List<File> exportShapes(AbortRetryIgnoreHandler handler, final String outdir, List<Tag> tags, final ShapeExportSettings settings, EventListener evl) throws IOException {
|
||||
List<File> ret = new ArrayList<>();
|
||||
if (tags.isEmpty()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
File foutdir = new File(outdir);
|
||||
Path.createDirectorySafe(foutdir);
|
||||
|
||||
int count = 0;
|
||||
for (Tag t : tags) {
|
||||
if (t instanceof ShapeTag) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int currentIndex = 1;
|
||||
for (final Tag t : tags) {
|
||||
if (t instanceof ShapeTag) {
|
||||
if (evl != null) {
|
||||
evl.handleExportingEvent("shape", currentIndex, count, t.getName());
|
||||
}
|
||||
|
||||
int characterID = 0;
|
||||
if (t instanceof CharacterTag) {
|
||||
characterID = ((CharacterTag) t).getCharacterId();
|
||||
}
|
||||
String ext = "svg";
|
||||
if (settings.mode == ShapeExportMode.PNG) {
|
||||
ext = "png";
|
||||
}
|
||||
if (settings.mode == ShapeExportMode.BMP) {
|
||||
ext = "bmp";
|
||||
}
|
||||
if (settings.mode == ShapeExportMode.CANVAS) {
|
||||
ext = "html";
|
||||
}
|
||||
|
||||
final File file = new File(outdir + File.separator + characterID + "." + ext);
|
||||
new RetryTask(() -> {
|
||||
ShapeTag st = (ShapeTag) t;
|
||||
switch (settings.mode) {
|
||||
case SVG:
|
||||
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
|
||||
ExportRectangle rect = new ExportRectangle(st.getRect());
|
||||
rect.xMax *= settings.zoom;
|
||||
rect.yMax *= settings.zoom;
|
||||
rect.xMin *= settings.zoom;
|
||||
rect.yMin *= settings.zoom;
|
||||
SVGExporter exporter = new SVGExporter(rect);
|
||||
st.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, settings.zoom);
|
||||
fos.write(Utf8Helper.getBytes(exporter.getSVG()));
|
||||
}
|
||||
break;
|
||||
case PNG:
|
||||
case BMP:
|
||||
RECT rect = st.getRect();
|
||||
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);
|
||||
img.fillTransparent();
|
||||
Matrix m = new Matrix();
|
||||
m.translate(-rect.Xmin, -rect.Ymin);
|
||||
m.scale(settings.zoom);
|
||||
st.toImage(0, 0, 0, new RenderContext(), img, m, new CXFORMWITHALPHA());
|
||||
if (settings.mode == ShapeExportMode.PNG) {
|
||||
ImageHelper.write(img.getBufferedImage(), "PNG", file);
|
||||
} else {
|
||||
BMPFile.saveBitmap(img.getBufferedImage(), file);
|
||||
}
|
||||
break;
|
||||
case CANVAS:
|
||||
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
|
||||
SHAPE shp = st.getShapes();
|
||||
int deltaX = -shp.getBounds().Xmin;
|
||||
int deltaY = -shp.getBounds().Ymin;
|
||||
CanvasShapeExporter cse = new CanvasShapeExporter(null, SWF.unitDivisor / settings.zoom, ((Tag) st).getSwf(), shp, new CXFORMWITHALPHA(), deltaX, deltaY);
|
||||
cse.export();
|
||||
Set<Integer> needed = new HashSet<>();
|
||||
needed.add(st.getCharacterId());
|
||||
st.getNeededCharactersDeep(needed);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
SWF.writeLibrary(st.getSwf(), needed, baos);
|
||||
fos.write(Utf8Helper.getBytes(cse.getHtml(new String(baos.toByteArray(), "UTF-8"))));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}, handler).run();
|
||||
ret.add(file);
|
||||
|
||||
if (evl != null) {
|
||||
evl.handleExportedEvent("shape", currentIndex, count, t.getName());
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
if (settings.mode == ShapeExportMode.CANVAS) {
|
||||
File fcanvas = new File(foutdir + File.separator + "canvas.js");
|
||||
Helper.saveStream(SWF.class.getClassLoader().getResourceAsStream("com/jpexs/helpers/resource/canvas.js"), fcanvas);
|
||||
ret.add(fcanvas);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2010-2015 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.exporters;
|
||||
|
||||
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
|
||||
import com.jpexs.decompiler.flash.EventListener;
|
||||
import com.jpexs.decompiler.flash.RetryTask;
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode;
|
||||
import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter;
|
||||
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.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
import com.jpexs.decompiler.flash.tags.base.ShapeTag;
|
||||
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
|
||||
import com.jpexs.decompiler.flash.types.RECT;
|
||||
import com.jpexs.decompiler.flash.types.SHAPE;
|
||||
import com.jpexs.helpers.Helper;
|
||||
import com.jpexs.helpers.Path;
|
||||
import com.jpexs.helpers.SerializableImage;
|
||||
import com.jpexs.helpers.utf8.Utf8Helper;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class ShapeExporter {
|
||||
|
||||
public List<File> exportShapes(AbortRetryIgnoreHandler handler, final String outdir, List<Tag> tags, final ShapeExportSettings settings, EventListener evl) throws IOException {
|
||||
List<File> ret = new ArrayList<>();
|
||||
if (tags.isEmpty()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
File foutdir = new File(outdir);
|
||||
Path.createDirectorySafe(foutdir);
|
||||
|
||||
int count = 0;
|
||||
for (Tag t : tags) {
|
||||
if (t instanceof ShapeTag) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int currentIndex = 1;
|
||||
for (final Tag t : tags) {
|
||||
if (t instanceof ShapeTag) {
|
||||
if (evl != null) {
|
||||
evl.handleExportingEvent("shape", currentIndex, count, t.getName());
|
||||
}
|
||||
|
||||
int characterID = 0;
|
||||
if (t instanceof CharacterTag) {
|
||||
characterID = ((CharacterTag) t).getCharacterId();
|
||||
}
|
||||
String ext = "svg";
|
||||
if (settings.mode == ShapeExportMode.PNG) {
|
||||
ext = "png";
|
||||
}
|
||||
if (settings.mode == ShapeExportMode.BMP) {
|
||||
ext = "bmp";
|
||||
}
|
||||
if (settings.mode == ShapeExportMode.CANVAS) {
|
||||
ext = "html";
|
||||
}
|
||||
|
||||
final File file = new File(outdir + File.separator + characterID + "." + ext);
|
||||
new RetryTask(() -> {
|
||||
ShapeTag st = (ShapeTag) t;
|
||||
switch (settings.mode) {
|
||||
case SVG:
|
||||
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
|
||||
ExportRectangle rect = new ExportRectangle(st.getRect());
|
||||
rect.xMax *= settings.zoom;
|
||||
rect.yMax *= settings.zoom;
|
||||
rect.xMin *= settings.zoom;
|
||||
rect.yMin *= settings.zoom;
|
||||
SVGExporter exporter = new SVGExporter(rect);
|
||||
st.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, settings.zoom);
|
||||
fos.write(Utf8Helper.getBytes(exporter.getSVG()));
|
||||
}
|
||||
break;
|
||||
case PNG:
|
||||
case BMP:
|
||||
RECT rect = st.getRect();
|
||||
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);
|
||||
img.fillTransparent();
|
||||
Matrix m = new Matrix();
|
||||
m.translate(-rect.Xmin, -rect.Ymin);
|
||||
m.scale(settings.zoom);
|
||||
st.toImage(0, 0, 0, new RenderContext(), img, m, new CXFORMWITHALPHA());
|
||||
if (settings.mode == ShapeExportMode.PNG) {
|
||||
ImageHelper.write(img.getBufferedImage(), "PNG", file);
|
||||
} else {
|
||||
BMPFile.saveBitmap(img.getBufferedImage(), file);
|
||||
}
|
||||
break;
|
||||
case CANVAS:
|
||||
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
|
||||
SHAPE shp = st.getShapes();
|
||||
int deltaX = -shp.getBounds().Xmin;
|
||||
int deltaY = -shp.getBounds().Ymin;
|
||||
CanvasShapeExporter cse = new CanvasShapeExporter(null, SWF.unitDivisor / settings.zoom, ((Tag) st).getSwf(), shp, new CXFORMWITHALPHA(), deltaX, deltaY);
|
||||
cse.export();
|
||||
Set<Integer> needed = new HashSet<>();
|
||||
needed.add(st.getCharacterId());
|
||||
st.getNeededCharactersDeep(needed);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
SWF.writeLibrary(st.getSwf(), needed, baos);
|
||||
fos.write(Utf8Helper.getBytes(cse.getHtml(new String(baos.toByteArray(), "UTF-8"), SWF.getTypePrefix(st) + st.getCharacterId(), st.getRect())));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}, handler).run();
|
||||
ret.add(file);
|
||||
|
||||
if (evl != null) {
|
||||
evl.handleExportedEvent("shape", currentIndex, count, t.getName());
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
if (settings.mode == ShapeExportMode.CANVAS) {
|
||||
File fcanvas = new File(foutdir + File.separator + "canvas.js");
|
||||
Helper.saveStream(SWF.class.getClassLoader().getResourceAsStream("com/jpexs/helpers/resource/canvas.js"), fcanvas);
|
||||
ret.add(fcanvas);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.jpexs.decompiler.flash.types.FILLSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.GRADIENT;
|
||||
import com.jpexs.decompiler.flash.types.GRADRECORD;
|
||||
import com.jpexs.decompiler.flash.types.LINESTYLE2;
|
||||
import com.jpexs.decompiler.flash.types.RECT;
|
||||
import com.jpexs.decompiler.flash.types.RGB;
|
||||
import com.jpexs.decompiler.flash.types.RGBA;
|
||||
import com.jpexs.decompiler.flash.types.SHAPE;
|
||||
@@ -94,16 +95,23 @@ public class CanvasMorphShapeExporter extends MorphShapeExporterBase {
|
||||
this.swf = swf;
|
||||
}
|
||||
|
||||
public String getHtml(String needed) {
|
||||
int width = (int) (Math.max(shape.getBounds().getWidth(), shapeEnd.getBounds().getWidth()) / unitDivisor);
|
||||
int height = (int) (Math.max(shape.getBounds().getHeight(), shapeEnd.getBounds().getHeight()) / unitDivisor);
|
||||
|
||||
return CanvasShapeExporter.getHtmlPrefix(width, height) + getJsPrefix() + needed + CanvasShapeExporter.getDrawJs(width, height, shapeData.toString()) + getJsSuffix(width, height) + CanvasShapeExporter.getHtmlSuffix();
|
||||
private String getDrawJs(int width, int height, String id, RECT rect) {
|
||||
return "var originalWidth=" + width + ";\r\nvar originalHeight=" + height + ";\r\n function drawFrame(ctx,ratio){\r\n"
|
||||
+ "\tctx.save();\r\n\tctx.transform(canvas.width/originalWidth,0,0,canvas.height/originalHeight,0,0);\r\n"
|
||||
+ "\tplace(\"" + id + "\",canvas,ctx,[" + (1 / unitDivisor) + ",0.0,0.0," + (1 / unitDivisor) + ","
|
||||
+ (-rect.Xmin / unitDivisor) + "," + (-rect.Ymin / unitDivisor) + "],ctrans,1,0,ratio,0);\r\n"
|
||||
+ "\tctx.restore();\r\n}\r\n";
|
||||
}
|
||||
|
||||
public static String getJsSuffix(int width, int height) {
|
||||
public String getHtml(String needed, String id, RECT rect) {
|
||||
int width = (int) (rect.getWidth() / unitDivisor);
|
||||
int height = (int) (rect.getHeight() / unitDivisor);
|
||||
|
||||
return CanvasShapeExporter.getHtmlPrefix(width, height) + getJsPrefix() + needed + getDrawJs(width, height, id, rect) + getJsSuffix(width, height) + CanvasShapeExporter.getHtmlSuffix();
|
||||
}
|
||||
|
||||
private static String getJsSuffix(int width, int height) {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
ret.append("}\r\n");
|
||||
int step = Math.round(65535 / 100);
|
||||
int rate = 10;
|
||||
ret.append("var step = ").append(step).append(";\r\n");
|
||||
@@ -111,16 +119,15 @@ public class CanvasMorphShapeExporter extends MorphShapeExporterBase {
|
||||
ret.append("function nextFrame(ctx){\r\n");
|
||||
ret.append("\tctx.clearRect(0,0,").append(width).append(",").append(height).append(");\r\n");
|
||||
ret.append("\tratio = (ratio+step)%65535;\r\n");
|
||||
ret.append("\tmorphshape(ctx,ratio);\r\n");
|
||||
ret.append("\tdrawFrame(ctx,ratio);\r\n");
|
||||
ret.append("}\r\n");
|
||||
ret.append("window.setInterval(function(){nextFrame(ctx)},").append(rate).append(");\r\n");
|
||||
ret.append(CanvasShapeExporter.getJsSuffix());
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
public static String getJsPrefix() {
|
||||
private static String getJsPrefix() {
|
||||
String ret = CanvasShapeExporter.getJsPrefix();
|
||||
ret += "function morphshape(ctx,ratio){\r\n";
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,17 +130,19 @@ public class CanvasShapeExporter extends ShapeExporterBase {
|
||||
+ "</html>";
|
||||
}
|
||||
|
||||
public static String getDrawJs(int width, int height, String data) {
|
||||
private String getDrawJs(int width, int height, String id, RECT rect) {
|
||||
return "var originalWidth=" + width + ";\r\nvar originalHeight=" + height + ";\r\n function drawFrame(){\r\n"
|
||||
+ "\tctx.save();\r\n\tctx.transform(canvas.width/originalWidth,0,0,canvas.height/originalHeight,0,0);\r\n" + data + "\tctx.restore();\r\n}\r\n\tdrawFrame();\r\n";
|
||||
+ "\tctx.save();\r\n\tctx.transform(canvas.width/originalWidth,0,0,canvas.height/originalHeight,0,0);\r\n"
|
||||
+ "\tplace(\"" + id + "\",canvas,ctx,[" + (1 / unitDivisor) + ",0.0,0.0," + (1 / unitDivisor) + ","
|
||||
+ (-rect.Xmin / unitDivisor) + "," + (-rect.Ymin / unitDivisor) + "],ctrans,1,0,0,0);\r\n"
|
||||
+ "\tctx.restore();\r\n}\r\n\tdrawFrame();\r\n";
|
||||
}
|
||||
|
||||
public String getHtml(String needed) {
|
||||
RECT r = shape.getBounds();
|
||||
int width = (int) (r.getWidth() / unitDivisor);
|
||||
int height = (int) (r.getHeight() / unitDivisor);
|
||||
public String getHtml(String needed, String id, RECT rect) {
|
||||
int width = (int) (rect.getWidth() / unitDivisor);
|
||||
int height = (int) (rect.getHeight() / unitDivisor);
|
||||
|
||||
return getHtmlPrefix(width, height) + getJsPrefix() + needed + getDrawJs(width, height, shapeData.toString()) + getJsSuffix() + getHtmlSuffix();
|
||||
return getHtmlPrefix(width, height) + getJsPrefix() + needed + getDrawJs(width, height, id, rect) + getJsSuffix() + getHtmlSuffix();
|
||||
}
|
||||
|
||||
public String getShapeData() {
|
||||
@@ -208,7 +210,7 @@ public class CanvasShapeExporter extends ShapeExporterBase {
|
||||
start.y += deltaY;
|
||||
end.x += deltaX;
|
||||
end.y += deltaY;
|
||||
fillData.append("\tvar grd=ctx.createLinearGradient(").append(Double.toString(start.x / unitDivisor)).append(",").append(Double.toString(start.y / unitDivisor)).append(",").append(Double.toString(end.x / unitDivisor)).append(",").append(Double.toString(end.y / unitDivisor)).append(");\r\n");
|
||||
fillData.append("\tvar grd=ctx.createLinearGradient(").append(start.x / unitDivisor).append(",").append(start.y / unitDivisor).append(",").append(end.x / unitDivisor).append(",").append(end.y / unitDivisor).append(");\r\n");
|
||||
} else {
|
||||
fillMatrix = matrix;
|
||||
fillData.append("\tvar grd=ctx.createRadialGradient(").append(focalPointRatio * 16384).append(",0,0,0,0,").append(16384 + 32768 * repeatCnt).append(");\r\n");
|
||||
@@ -225,7 +227,7 @@ public class CanvasShapeExporter extends ShapeExporterBase {
|
||||
revert = !revert;
|
||||
}
|
||||
for (GRADRECORD r : gradientRecords) {
|
||||
fillData.append("\tgrd.addColorStop(").append(Double.toString(pos + (oneHeight * (revert ? 255 - r.ratio : r.ratio) / 255.0))).append(",").append(color(r.color)).append(");\r\n");
|
||||
fillData.append("\tgrd.addColorStop(").append(pos + (oneHeight * (revert ? 255 - r.ratio : r.ratio) / 255.0)).append(",").append(color(r.color)).append(");\r\n");
|
||||
lastRadColor = color(r.color);
|
||||
}
|
||||
pos += oneHeight;
|
||||
|
||||
@@ -1,432 +1,431 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2015 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;
|
||||
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.SWFInputStream;
|
||||
import com.jpexs.decompiler.flash.SWFOutputStream;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.CanvasMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.SVGMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter;
|
||||
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.MorphShapeTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
import com.jpexs.decompiler.flash.types.BasicType;
|
||||
import com.jpexs.decompiler.flash.types.ColorTransform;
|
||||
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.MORPHLINESTYLE2;
|
||||
import com.jpexs.decompiler.flash.types.MORPHLINESTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.RECT;
|
||||
import com.jpexs.decompiler.flash.types.SHAPE;
|
||||
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.annotations.Reserved;
|
||||
import com.jpexs.decompiler.flash.types.annotations.SWFType;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
|
||||
import com.jpexs.helpers.ByteArrayRange;
|
||||
import com.jpexs.helpers.SerializableImage;
|
||||
import java.awt.Shape;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class DefineMorphShape2Tag extends MorphShapeTag {
|
||||
|
||||
@SWFType(BasicType.UI16)
|
||||
public int characterId;
|
||||
|
||||
public RECT startBounds;
|
||||
|
||||
public RECT endBounds;
|
||||
|
||||
public RECT startEdgeBounds;
|
||||
|
||||
public RECT endEdgeBounds;
|
||||
|
||||
@Reserved
|
||||
@SWFType(value = BasicType.UB, count = 6)
|
||||
public int reserved;
|
||||
|
||||
public boolean usesNonScalingStrokes;
|
||||
|
||||
public boolean usesScalingStrokes;
|
||||
|
||||
public MORPHFILLSTYLEARRAY morphFillStyles;
|
||||
|
||||
public MORPHLINESTYLEARRAY morphLineStyles;
|
||||
|
||||
public SHAPE startEdges;
|
||||
|
||||
public SHAPE endEdges;
|
||||
|
||||
public static final int ID = 84;
|
||||
|
||||
public static final int MAX_RATIO = 65535;
|
||||
|
||||
@Override
|
||||
public void getNeededCharacters(Set<Integer> needed) {
|
||||
morphFillStyles.getNeededCharacters(needed);
|
||||
startEdges.getNeededCharacters(needed);
|
||||
endEdges.getNeededCharacters(needed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceCharacter(int oldCharacterId, int newCharacterId) {
|
||||
boolean modified = false;
|
||||
modified |= morphFillStyles.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
modified |= startEdges.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
modified |= endEdges.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
if (modified) {
|
||||
setModified(true);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeCharacter(int characterId) {
|
||||
boolean modified = false;
|
||||
modified |= morphFillStyles.removeCharacter(characterId);
|
||||
modified |= startEdges.removeCharacter(characterId);
|
||||
modified |= endEdges.removeCharacter(characterId);
|
||||
if (modified) {
|
||||
setModified(true);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getRect(Set<BoundedTag> added) {
|
||||
RECT rect = new RECT();
|
||||
rect.Xmin = Math.min(startBounds.Xmin, endBounds.Xmin);
|
||||
rect.Ymin = Math.min(startBounds.Ymin, endBounds.Ymin);
|
||||
rect.Xmax = Math.max(startBounds.Xmax, endBounds.Xmax);
|
||||
rect.Ymax = Math.max(startBounds.Ymax, endBounds.Ymax);
|
||||
return rect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharacterId() {
|
||||
return characterId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data bytes
|
||||
*
|
||||
* @return Bytes of data
|
||||
*/
|
||||
@Override
|
||||
public byte[] getData() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
OutputStream os = baos;
|
||||
SWFOutputStream sos = new SWFOutputStream(os, getVersion());
|
||||
try {
|
||||
sos.writeUI16(characterId);
|
||||
sos.writeRECT(startBounds);
|
||||
sos.writeRECT(endBounds);
|
||||
sos.writeRECT(startEdgeBounds);
|
||||
sos.writeRECT(endEdgeBounds);
|
||||
sos.writeUB(6, reserved);
|
||||
sos.writeUB(1, usesNonScalingStrokes ? 1 : 0);
|
||||
sos.writeUB(1, usesScalingStrokes ? 1 : 0);
|
||||
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
||||
SWFOutputStream sos2 = new SWFOutputStream(baos2, getVersion());
|
||||
sos2.writeMORPHFILLSTYLEARRAY(morphFillStyles, 2);
|
||||
sos2.writeMORPHLINESTYLEARRAY(morphLineStyles, 2);
|
||||
sos2.writeSHAPE(startEdges, 2);
|
||||
byte[] ba2 = baos2.toByteArray();
|
||||
sos.writeUI32(ba2.length);
|
||||
sos.write(ba2);
|
||||
sos.writeSHAPE(endEdges, 2);
|
||||
} catch (IOException e) {
|
||||
throw new Error("This should never happen.", e);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param swf
|
||||
*/
|
||||
public DefineMorphShape2Tag(SWF swf) {
|
||||
super(swf, ID, "DefineMorphShape2", null);
|
||||
characterId = swf.getNextCharacterId();
|
||||
startBounds = new RECT();
|
||||
endBounds = new RECT();
|
||||
startEdgeBounds = new RECT();
|
||||
endEdgeBounds = new RECT();
|
||||
startEdges = SHAPE.createEmpty(2);
|
||||
endEdges = SHAPE.createEmpty(2);
|
||||
morphFillStyles = new MORPHFILLSTYLEARRAY();
|
||||
morphFillStyles.fillStyles = new MORPHFILLSTYLE[0];
|
||||
morphLineStyles = new MORPHLINESTYLEARRAY();
|
||||
morphLineStyles.lineStyles2 = new MORPHLINESTYLE2[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param sis
|
||||
* @param data
|
||||
* @throws IOException
|
||||
*/
|
||||
public DefineMorphShape2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException {
|
||||
super(sis.getSwf(), ID, "DefineMorphShape2", data);
|
||||
readData(sis, data, 0, false, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException {
|
||||
characterId = sis.readUI16("characterId");
|
||||
startBounds = sis.readRECT("startBounds");
|
||||
endBounds = sis.readRECT("endBounds");
|
||||
startEdgeBounds = sis.readRECT("startEdgeBounds");
|
||||
endEdgeBounds = sis.readRECT("endEdgeBounds");
|
||||
reserved = (int) sis.readUB(6, "reserved");
|
||||
usesNonScalingStrokes = sis.readUB(1, "usesNonScalingStrokes") == 1;
|
||||
usesScalingStrokes = sis.readUB(1, "usesScalingStrokes") == 1;
|
||||
long offset = sis.readUI32("offset");
|
||||
morphFillStyles = sis.readMORPHFILLSTYLEARRAY("morphFillStyles");
|
||||
morphLineStyles = sis.readMORPHLINESTYLEARRAY(2, "morphLineStyles");
|
||||
startEdges = sis.readSHAPE(2, true, "startEdges");
|
||||
endEdges = sis.readSHAPE(2, true, "endEdges");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getStartBounds() {
|
||||
return startBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getEndBounds() {
|
||||
return endBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MORPHFILLSTYLEARRAY getFillStyles() {
|
||||
return morphFillStyles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MORPHLINESTYLEARRAY getLineStyles() {
|
||||
return morphLineStyles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPE getStartEdges() {
|
||||
return startEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPE getEndEdges() {
|
||||
return endEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShapeNum() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPEWITHSTYLE getShapeAtRatio(int ratio) {
|
||||
List<SHAPERECORD> finalRecords = new ArrayList<>();
|
||||
FILLSTYLEARRAY fillStyles = morphFillStyles.getFillStylesAt(ratio);
|
||||
LINESTYLEARRAY lineStyles = morphLineStyles.getLineStylesAt(getShapeNum(), ratio);
|
||||
|
||||
int startPosX = 0, startPosY = 0;
|
||||
int endPosX = 0, endPosY = 0;
|
||||
int posX = 0, posY = 0;
|
||||
|
||||
for (int startIndex = 0, endIndex = 0;
|
||||
startIndex < startEdges.shapeRecords.size()
|
||||
&& endIndex < endEdges.shapeRecords.size(); startIndex++, endIndex++) {
|
||||
|
||||
SHAPERECORD edge1 = startEdges.shapeRecords.get(startIndex);
|
||||
SHAPERECORD edge2 = endEdges.shapeRecords.get(endIndex);
|
||||
if (edge1 instanceof StyleChangeRecord || edge2 instanceof StyleChangeRecord) {
|
||||
StyleChangeRecord scr1;
|
||||
if (edge1 instanceof StyleChangeRecord) {
|
||||
scr1 = (StyleChangeRecord) edge1;
|
||||
if (scr1.stateMoveTo) {
|
||||
startPosX = scr1.moveDeltaX;
|
||||
startPosY = scr1.moveDeltaY;
|
||||
}
|
||||
} else {
|
||||
scr1 = new StyleChangeRecord();
|
||||
startIndex--;
|
||||
}
|
||||
StyleChangeRecord scr2;
|
||||
if (edge2 instanceof StyleChangeRecord) {
|
||||
scr2 = (StyleChangeRecord) edge2;
|
||||
if (scr2.stateMoveTo) {
|
||||
endPosX = scr2.moveDeltaX;
|
||||
endPosY = scr2.moveDeltaY;
|
||||
}
|
||||
} else {
|
||||
scr2 = new StyleChangeRecord();
|
||||
endIndex--;
|
||||
}
|
||||
StyleChangeRecord scr = scr1.clone();
|
||||
if (scr1.stateMoveTo || scr2.stateMoveTo) {
|
||||
scr.moveDeltaX = startPosX + (endPosX - startPosX) * ratio / 65535;
|
||||
scr.moveDeltaY = startPosY + (endPosY - startPosY) * ratio / 65535;
|
||||
scr.stateMoveTo = scr.moveDeltaX != posX || scr.moveDeltaY != posY;
|
||||
}
|
||||
finalRecords.add(scr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (edge1 instanceof EndShapeRecord) {
|
||||
finalRecords.add(edge1);
|
||||
break;
|
||||
}
|
||||
if (edge2 instanceof EndShapeRecord) {
|
||||
finalRecords.add(edge2);
|
||||
break;
|
||||
}
|
||||
|
||||
if (edge1 instanceof CurvedEdgeRecord || edge2 instanceof CurvedEdgeRecord) {
|
||||
CurvedEdgeRecord cer1 = null;
|
||||
if (edge1 instanceof CurvedEdgeRecord) {
|
||||
cer1 = (CurvedEdgeRecord) edge1;
|
||||
} else if (edge1 instanceof StraightEdgeRecord) {
|
||||
cer1 = SHAPERECORD.straightToCurve((StraightEdgeRecord) edge1);
|
||||
}
|
||||
CurvedEdgeRecord cer2 = null;
|
||||
if (edge2 instanceof CurvedEdgeRecord) {
|
||||
cer2 = (CurvedEdgeRecord) edge2;
|
||||
} else if (edge2 instanceof StraightEdgeRecord) {
|
||||
cer2 = SHAPERECORD.straightToCurve((StraightEdgeRecord) edge2);
|
||||
}
|
||||
if ((cer2 == null) || (cer1 == null)) {
|
||||
continue;
|
||||
}
|
||||
CurvedEdgeRecord cer = new CurvedEdgeRecord();
|
||||
cer.controlDeltaX = cer1.controlDeltaX + (cer2.controlDeltaX - cer1.controlDeltaX) * ratio / 65535;
|
||||
cer.controlDeltaY = cer1.controlDeltaY + (cer2.controlDeltaY - cer1.controlDeltaY) * ratio / 65535;
|
||||
cer.anchorDeltaX = cer1.anchorDeltaX + (cer2.anchorDeltaX - cer1.anchorDeltaX) * ratio / 65535;
|
||||
cer.anchorDeltaY = cer1.anchorDeltaY + (cer2.anchorDeltaY - cer1.anchorDeltaY) * ratio / 65535;
|
||||
startPosX += cer1.controlDeltaX + cer1.anchorDeltaX;
|
||||
startPosY += cer1.controlDeltaY + cer1.anchorDeltaY;
|
||||
endPosX += cer2.controlDeltaX + cer2.anchorDeltaX;
|
||||
endPosY += cer2.controlDeltaY + cer2.anchorDeltaY;
|
||||
posX += cer.controlDeltaX + cer.anchorDeltaX;
|
||||
posY += cer.controlDeltaY + cer.anchorDeltaY;
|
||||
finalRecords.add(cer);
|
||||
} else {
|
||||
StraightEdgeRecord ser1 = null;
|
||||
if (edge1 instanceof StraightEdgeRecord) {
|
||||
ser1 = (StraightEdgeRecord) edge1;
|
||||
}
|
||||
StraightEdgeRecord ser2 = null;
|
||||
if (edge2 instanceof StraightEdgeRecord) {
|
||||
ser2 = (StraightEdgeRecord) edge2;
|
||||
}
|
||||
if ((ser2 == null) || (ser1 == null)) {
|
||||
continue;
|
||||
}
|
||||
StraightEdgeRecord ser = new StraightEdgeRecord();
|
||||
ser.generalLineFlag = true;
|
||||
ser.vertLineFlag = false;
|
||||
ser.deltaX = ser1.deltaX + (ser2.deltaX - ser1.deltaX) * ratio / 65535;
|
||||
ser.deltaY = ser1.deltaY + (ser2.deltaY - ser1.deltaY) * ratio / 65535;
|
||||
startPosX += ser1.deltaX;
|
||||
startPosY += ser1.deltaY;
|
||||
endPosX += ser2.deltaX;
|
||||
endPosY += ser2.deltaY;
|
||||
posX += ser.deltaX;
|
||||
posY += ser.deltaX;
|
||||
finalRecords.add(ser);
|
||||
}
|
||||
}
|
||||
SHAPEWITHSTYLE shape = new SHAPEWITHSTYLE();
|
||||
shape.fillStyles = fillStyles;
|
||||
shape.lineStyles = lineStyles;
|
||||
shape.shapeRecords = finalRecords;
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, Matrix transformation, ColorTransform colorTransform) {
|
||||
SHAPEWITHSTYLE shape = getShapeAtRatio(ratio);
|
||||
// shapeNum: 4
|
||||
// todo: Currently the generated image is not cached, because the cache
|
||||
// key contains the hashCode of the finalRecord object, and it is always
|
||||
// recreated
|
||||
BitmapExporter.export(swf, shape, null, image, transformation, colorTransform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, double zoom) {
|
||||
if (ratio == -2) {
|
||||
SHAPEWITHSTYLE beginShapes = getShapeAtRatio(0);
|
||||
SHAPEWITHSTYLE endShapes = getShapeAtRatio(65535);
|
||||
SVGMorphShapeExporter shapeExporter = new SVGMorphShapeExporter(swf, beginShapes, endShapes, exporter, null, colorTransform, zoom);
|
||||
shapeExporter.export();
|
||||
} else {
|
||||
SHAPEWITHSTYLE shapes = getShapeAtRatio(ratio);
|
||||
SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shapes, exporter, null, colorTransform, zoom);
|
||||
shapeExporter.export();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumFrames() {
|
||||
return 65536;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleFrame() {
|
||||
// Morpshape is a single frame specified with the ratio
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
|
||||
return transformation.toTransform().createTransformedShape(getShapeAtRatio(ratio).getOutline());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtmlCanvas(double unitDivisor) {
|
||||
CanvasMorphShapeExporter cmse = new CanvasMorphShapeExporter(swf, getShapeAtRatio(0), getShapeAtRatio(MAX_RATIO), new ColorTransform(), unitDivisor, 0, 0);
|
||||
cmse.export();
|
||||
|
||||
return cmse.getShapeData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterId(int characterId) {
|
||||
this.characterId = characterId;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2010-2015 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;
|
||||
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.SWFInputStream;
|
||||
import com.jpexs.decompiler.flash.SWFOutputStream;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.CanvasMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.SVGMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter;
|
||||
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.MorphShapeTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
import com.jpexs.decompiler.flash.types.BasicType;
|
||||
import com.jpexs.decompiler.flash.types.ColorTransform;
|
||||
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.MORPHLINESTYLE2;
|
||||
import com.jpexs.decompiler.flash.types.MORPHLINESTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.RECT;
|
||||
import com.jpexs.decompiler.flash.types.SHAPE;
|
||||
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.annotations.Reserved;
|
||||
import com.jpexs.decompiler.flash.types.annotations.SWFType;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
|
||||
import com.jpexs.helpers.ByteArrayRange;
|
||||
import com.jpexs.helpers.SerializableImage;
|
||||
import java.awt.Shape;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class DefineMorphShape2Tag extends MorphShapeTag {
|
||||
|
||||
@SWFType(BasicType.UI16)
|
||||
public int characterId;
|
||||
|
||||
public RECT startBounds;
|
||||
|
||||
public RECT endBounds;
|
||||
|
||||
public RECT startEdgeBounds;
|
||||
|
||||
public RECT endEdgeBounds;
|
||||
|
||||
@Reserved
|
||||
@SWFType(value = BasicType.UB, count = 6)
|
||||
public int reserved;
|
||||
|
||||
public boolean usesNonScalingStrokes;
|
||||
|
||||
public boolean usesScalingStrokes;
|
||||
|
||||
public MORPHFILLSTYLEARRAY morphFillStyles;
|
||||
|
||||
public MORPHLINESTYLEARRAY morphLineStyles;
|
||||
|
||||
public SHAPE startEdges;
|
||||
|
||||
public SHAPE endEdges;
|
||||
|
||||
public static final int ID = 84;
|
||||
|
||||
public static final int MAX_RATIO = 65535;
|
||||
|
||||
@Override
|
||||
public void getNeededCharacters(Set<Integer> needed) {
|
||||
morphFillStyles.getNeededCharacters(needed);
|
||||
startEdges.getNeededCharacters(needed);
|
||||
endEdges.getNeededCharacters(needed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceCharacter(int oldCharacterId, int newCharacterId) {
|
||||
boolean modified = false;
|
||||
modified |= morphFillStyles.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
modified |= startEdges.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
modified |= endEdges.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
if (modified) {
|
||||
setModified(true);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeCharacter(int characterId) {
|
||||
boolean modified = false;
|
||||
modified |= morphFillStyles.removeCharacter(characterId);
|
||||
modified |= startEdges.removeCharacter(characterId);
|
||||
modified |= endEdges.removeCharacter(characterId);
|
||||
if (modified) {
|
||||
setModified(true);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getRect(Set<BoundedTag> added) {
|
||||
RECT rect = new RECT();
|
||||
rect.Xmin = Math.min(startBounds.Xmin, endBounds.Xmin);
|
||||
rect.Ymin = Math.min(startBounds.Ymin, endBounds.Ymin);
|
||||
rect.Xmax = Math.max(startBounds.Xmax, endBounds.Xmax);
|
||||
rect.Ymax = Math.max(startBounds.Ymax, endBounds.Ymax);
|
||||
return rect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharacterId() {
|
||||
return characterId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data bytes
|
||||
*
|
||||
* @return Bytes of data
|
||||
*/
|
||||
@Override
|
||||
public byte[] getData() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
OutputStream os = baos;
|
||||
SWFOutputStream sos = new SWFOutputStream(os, getVersion());
|
||||
try {
|
||||
sos.writeUI16(characterId);
|
||||
sos.writeRECT(startBounds);
|
||||
sos.writeRECT(endBounds);
|
||||
sos.writeRECT(startEdgeBounds);
|
||||
sos.writeRECT(endEdgeBounds);
|
||||
sos.writeUB(6, reserved);
|
||||
sos.writeUB(1, usesNonScalingStrokes ? 1 : 0);
|
||||
sos.writeUB(1, usesScalingStrokes ? 1 : 0);
|
||||
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
||||
SWFOutputStream sos2 = new SWFOutputStream(baos2, getVersion());
|
||||
sos2.writeMORPHFILLSTYLEARRAY(morphFillStyles, 2);
|
||||
sos2.writeMORPHLINESTYLEARRAY(morphLineStyles, 2);
|
||||
sos2.writeSHAPE(startEdges, 2);
|
||||
byte[] ba2 = baos2.toByteArray();
|
||||
sos.writeUI32(ba2.length);
|
||||
sos.write(ba2);
|
||||
sos.writeSHAPE(endEdges, 2);
|
||||
} catch (IOException e) {
|
||||
throw new Error("This should never happen.", e);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param swf
|
||||
*/
|
||||
public DefineMorphShape2Tag(SWF swf) {
|
||||
super(swf, ID, "DefineMorphShape2", null);
|
||||
characterId = swf.getNextCharacterId();
|
||||
startBounds = new RECT();
|
||||
endBounds = new RECT();
|
||||
startEdgeBounds = new RECT();
|
||||
endEdgeBounds = new RECT();
|
||||
startEdges = SHAPE.createEmpty(2);
|
||||
endEdges = SHAPE.createEmpty(2);
|
||||
morphFillStyles = new MORPHFILLSTYLEARRAY();
|
||||
morphFillStyles.fillStyles = new MORPHFILLSTYLE[0];
|
||||
morphLineStyles = new MORPHLINESTYLEARRAY();
|
||||
morphLineStyles.lineStyles2 = new MORPHLINESTYLE2[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param sis
|
||||
* @param data
|
||||
* @throws IOException
|
||||
*/
|
||||
public DefineMorphShape2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException {
|
||||
super(sis.getSwf(), ID, "DefineMorphShape2", data);
|
||||
readData(sis, data, 0, false, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException {
|
||||
characterId = sis.readUI16("characterId");
|
||||
startBounds = sis.readRECT("startBounds");
|
||||
endBounds = sis.readRECT("endBounds");
|
||||
startEdgeBounds = sis.readRECT("startEdgeBounds");
|
||||
endEdgeBounds = sis.readRECT("endEdgeBounds");
|
||||
reserved = (int) sis.readUB(6, "reserved");
|
||||
usesNonScalingStrokes = sis.readUB(1, "usesNonScalingStrokes") == 1;
|
||||
usesScalingStrokes = sis.readUB(1, "usesScalingStrokes") == 1;
|
||||
long offset = sis.readUI32("offset");
|
||||
morphFillStyles = sis.readMORPHFILLSTYLEARRAY("morphFillStyles");
|
||||
morphLineStyles = sis.readMORPHLINESTYLEARRAY(2, "morphLineStyles");
|
||||
startEdges = sis.readSHAPE(2, true, "startEdges");
|
||||
endEdges = sis.readSHAPE(2, true, "endEdges");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getStartBounds() {
|
||||
return startBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getEndBounds() {
|
||||
return endBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MORPHFILLSTYLEARRAY getFillStyles() {
|
||||
return morphFillStyles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MORPHLINESTYLEARRAY getLineStyles() {
|
||||
return morphLineStyles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPE getStartEdges() {
|
||||
return startEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPE getEndEdges() {
|
||||
return endEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShapeNum() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPEWITHSTYLE getShapeAtRatio(int ratio) {
|
||||
List<SHAPERECORD> finalRecords = new ArrayList<>();
|
||||
FILLSTYLEARRAY fillStyles = morphFillStyles.getFillStylesAt(ratio);
|
||||
LINESTYLEARRAY lineStyles = morphLineStyles.getLineStylesAt(getShapeNum(), ratio);
|
||||
|
||||
int startPosX = 0, startPosY = 0;
|
||||
int endPosX = 0, endPosY = 0;
|
||||
int posX = 0, posY = 0;
|
||||
|
||||
for (int startIndex = 0, endIndex = 0;
|
||||
startIndex < startEdges.shapeRecords.size()
|
||||
&& endIndex < endEdges.shapeRecords.size(); startIndex++, endIndex++) {
|
||||
|
||||
SHAPERECORD edge1 = startEdges.shapeRecords.get(startIndex);
|
||||
SHAPERECORD edge2 = endEdges.shapeRecords.get(endIndex);
|
||||
if (edge1 instanceof StyleChangeRecord || edge2 instanceof StyleChangeRecord) {
|
||||
StyleChangeRecord scr1;
|
||||
if (edge1 instanceof StyleChangeRecord) {
|
||||
scr1 = (StyleChangeRecord) edge1;
|
||||
if (scr1.stateMoveTo) {
|
||||
startPosX = scr1.moveDeltaX;
|
||||
startPosY = scr1.moveDeltaY;
|
||||
}
|
||||
} else {
|
||||
scr1 = new StyleChangeRecord();
|
||||
startIndex--;
|
||||
}
|
||||
StyleChangeRecord scr2;
|
||||
if (edge2 instanceof StyleChangeRecord) {
|
||||
scr2 = (StyleChangeRecord) edge2;
|
||||
if (scr2.stateMoveTo) {
|
||||
endPosX = scr2.moveDeltaX;
|
||||
endPosY = scr2.moveDeltaY;
|
||||
}
|
||||
} else {
|
||||
scr2 = new StyleChangeRecord();
|
||||
endIndex--;
|
||||
}
|
||||
StyleChangeRecord scr = scr1.clone();
|
||||
if (scr1.stateMoveTo || scr2.stateMoveTo) {
|
||||
scr.moveDeltaX = startPosX + (endPosX - startPosX) * ratio / 65535;
|
||||
scr.moveDeltaY = startPosY + (endPosY - startPosY) * ratio / 65535;
|
||||
scr.stateMoveTo = scr.moveDeltaX != posX || scr.moveDeltaY != posY;
|
||||
}
|
||||
finalRecords.add(scr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (edge1 instanceof EndShapeRecord) {
|
||||
finalRecords.add(edge1);
|
||||
break;
|
||||
}
|
||||
if (edge2 instanceof EndShapeRecord) {
|
||||
finalRecords.add(edge2);
|
||||
break;
|
||||
}
|
||||
|
||||
if (edge1 instanceof CurvedEdgeRecord || edge2 instanceof CurvedEdgeRecord) {
|
||||
CurvedEdgeRecord cer1 = null;
|
||||
if (edge1 instanceof CurvedEdgeRecord) {
|
||||
cer1 = (CurvedEdgeRecord) edge1;
|
||||
} else if (edge1 instanceof StraightEdgeRecord) {
|
||||
cer1 = SHAPERECORD.straightToCurve((StraightEdgeRecord) edge1);
|
||||
}
|
||||
CurvedEdgeRecord cer2 = null;
|
||||
if (edge2 instanceof CurvedEdgeRecord) {
|
||||
cer2 = (CurvedEdgeRecord) edge2;
|
||||
} else if (edge2 instanceof StraightEdgeRecord) {
|
||||
cer2 = SHAPERECORD.straightToCurve((StraightEdgeRecord) edge2);
|
||||
}
|
||||
if ((cer2 == null) || (cer1 == null)) {
|
||||
continue;
|
||||
}
|
||||
CurvedEdgeRecord cer = new CurvedEdgeRecord();
|
||||
cer.controlDeltaX = cer1.controlDeltaX + (cer2.controlDeltaX - cer1.controlDeltaX) * ratio / 65535;
|
||||
cer.controlDeltaY = cer1.controlDeltaY + (cer2.controlDeltaY - cer1.controlDeltaY) * ratio / 65535;
|
||||
cer.anchorDeltaX = cer1.anchorDeltaX + (cer2.anchorDeltaX - cer1.anchorDeltaX) * ratio / 65535;
|
||||
cer.anchorDeltaY = cer1.anchorDeltaY + (cer2.anchorDeltaY - cer1.anchorDeltaY) * ratio / 65535;
|
||||
startPosX += cer1.controlDeltaX + cer1.anchorDeltaX;
|
||||
startPosY += cer1.controlDeltaY + cer1.anchorDeltaY;
|
||||
endPosX += cer2.controlDeltaX + cer2.anchorDeltaX;
|
||||
endPosY += cer2.controlDeltaY + cer2.anchorDeltaY;
|
||||
posX += cer.controlDeltaX + cer.anchorDeltaX;
|
||||
posY += cer.controlDeltaY + cer.anchorDeltaY;
|
||||
finalRecords.add(cer);
|
||||
} else {
|
||||
StraightEdgeRecord ser1 = null;
|
||||
if (edge1 instanceof StraightEdgeRecord) {
|
||||
ser1 = (StraightEdgeRecord) edge1;
|
||||
}
|
||||
StraightEdgeRecord ser2 = null;
|
||||
if (edge2 instanceof StraightEdgeRecord) {
|
||||
ser2 = (StraightEdgeRecord) edge2;
|
||||
}
|
||||
if ((ser2 == null) || (ser1 == null)) {
|
||||
continue;
|
||||
}
|
||||
StraightEdgeRecord ser = new StraightEdgeRecord();
|
||||
ser.generalLineFlag = true;
|
||||
ser.vertLineFlag = false;
|
||||
ser.deltaX = ser1.deltaX + (ser2.deltaX - ser1.deltaX) * ratio / 65535;
|
||||
ser.deltaY = ser1.deltaY + (ser2.deltaY - ser1.deltaY) * ratio / 65535;
|
||||
startPosX += ser1.deltaX;
|
||||
startPosY += ser1.deltaY;
|
||||
endPosX += ser2.deltaX;
|
||||
endPosY += ser2.deltaY;
|
||||
posX += ser.deltaX;
|
||||
posY += ser.deltaX;
|
||||
finalRecords.add(ser);
|
||||
}
|
||||
}
|
||||
SHAPEWITHSTYLE shape = new SHAPEWITHSTYLE();
|
||||
shape.fillStyles = fillStyles;
|
||||
shape.lineStyles = lineStyles;
|
||||
shape.shapeRecords = finalRecords;
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, Matrix transformation, ColorTransform colorTransform) {
|
||||
SHAPEWITHSTYLE shape = getShapeAtRatio(ratio);
|
||||
// shapeNum: 4
|
||||
// todo: Currently the generated image is not cached, because the cache
|
||||
// key contains the hashCode of the finalRecord object, and it is always
|
||||
// recreated
|
||||
BitmapExporter.export(swf, shape, null, image, transformation, colorTransform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, double zoom) {
|
||||
if (ratio == -2) {
|
||||
SHAPEWITHSTYLE beginShapes = getShapeAtRatio(0);
|
||||
SHAPEWITHSTYLE endShapes = getShapeAtRatio(65535);
|
||||
SVGMorphShapeExporter shapeExporter = new SVGMorphShapeExporter(swf, beginShapes, endShapes, exporter, null, colorTransform, zoom);
|
||||
shapeExporter.export();
|
||||
} else {
|
||||
SHAPEWITHSTYLE shapes = getShapeAtRatio(ratio);
|
||||
SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shapes, exporter, null, colorTransform, zoom);
|
||||
shapeExporter.export();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumFrames() {
|
||||
return 65536;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleFrame() {
|
||||
// Morpshape is a single frame specified with the ratio
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
|
||||
return transformation.toTransform().createTransformedShape(getShapeAtRatio(ratio).getOutline());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtmlCanvas(double unitDivisor) {
|
||||
CanvasMorphShapeExporter cmse = new CanvasMorphShapeExporter(swf, getShapeAtRatio(0), getShapeAtRatio(MAX_RATIO), new ColorTransform(), unitDivisor, 0, 0);
|
||||
cmse.export();
|
||||
return cmse.getShapeData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterId(int characterId) {
|
||||
this.characterId = characterId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,408 +1,407 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2015 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;
|
||||
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.SWFInputStream;
|
||||
import com.jpexs.decompiler.flash.SWFOutputStream;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.CanvasMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.SVGMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter;
|
||||
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.MorphShapeTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
import com.jpexs.decompiler.flash.types.BasicType;
|
||||
import com.jpexs.decompiler.flash.types.ColorTransform;
|
||||
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.MORPHLINESTYLE;
|
||||
import com.jpexs.decompiler.flash.types.MORPHLINESTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.RECT;
|
||||
import com.jpexs.decompiler.flash.types.SHAPE;
|
||||
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.annotations.SWFType;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
|
||||
import com.jpexs.helpers.ByteArrayRange;
|
||||
import com.jpexs.helpers.SerializableImage;
|
||||
import java.awt.Shape;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class DefineMorphShapeTag extends MorphShapeTag {
|
||||
|
||||
@SWFType(BasicType.UI16)
|
||||
public int characterId;
|
||||
|
||||
public RECT startBounds;
|
||||
|
||||
public RECT endBounds;
|
||||
|
||||
public MORPHFILLSTYLEARRAY morphFillStyles;
|
||||
|
||||
public MORPHLINESTYLEARRAY morphLineStyles;
|
||||
|
||||
public SHAPE startEdges;
|
||||
|
||||
public SHAPE endEdges;
|
||||
|
||||
public static final int ID = 46;
|
||||
|
||||
public static final int MAX_RATIO = 65535;
|
||||
|
||||
@Override
|
||||
public void getNeededCharacters(Set<Integer> needed) {
|
||||
morphFillStyles.getNeededCharacters(needed);
|
||||
startEdges.getNeededCharacters(needed);
|
||||
endEdges.getNeededCharacters(needed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceCharacter(int oldCharacterId, int newCharacterId) {
|
||||
boolean modified = false;
|
||||
modified |= morphFillStyles.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
modified |= startEdges.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
modified |= endEdges.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
if (modified) {
|
||||
setModified(true);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeCharacter(int characterId) {
|
||||
boolean modified = false;
|
||||
modified |= morphFillStyles.removeCharacter(characterId);
|
||||
modified |= startEdges.removeCharacter(characterId);
|
||||
modified |= endEdges.removeCharacter(characterId);
|
||||
if (modified) {
|
||||
setModified(true);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharacterId() {
|
||||
return characterId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data bytes
|
||||
*
|
||||
* @return Bytes of data
|
||||
*/
|
||||
@Override
|
||||
public byte[] getData() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
OutputStream os = baos;
|
||||
SWFOutputStream sos = new SWFOutputStream(os, getVersion());
|
||||
try {
|
||||
sos.writeUI16(characterId);
|
||||
sos.writeRECT(startBounds);
|
||||
sos.writeRECT(endBounds);
|
||||
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
||||
SWFOutputStream sos2 = new SWFOutputStream(baos2, getVersion());
|
||||
sos2.writeMORPHFILLSTYLEARRAY(morphFillStyles, 1);
|
||||
sos2.writeMORPHLINESTYLEARRAY(morphLineStyles, 1);
|
||||
sos2.writeSHAPE(startEdges, 1);
|
||||
byte[] d = baos2.toByteArray();
|
||||
sos.writeUI32(d.length);
|
||||
sos.write(d);
|
||||
sos.writeSHAPE(endEdges, 1);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new Error("This should never happen.", e);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param swf
|
||||
*/
|
||||
public DefineMorphShapeTag(SWF swf) {
|
||||
super(swf, ID, "DefineMorphShape", null);
|
||||
characterId = swf.getNextCharacterId();
|
||||
startBounds = new RECT();
|
||||
endBounds = new RECT();
|
||||
startEdges = SHAPE.createEmpty(1);
|
||||
endEdges = SHAPE.createEmpty(1);
|
||||
morphFillStyles = new MORPHFILLSTYLEARRAY();
|
||||
morphFillStyles.fillStyles = new MORPHFILLSTYLE[0];
|
||||
morphLineStyles = new MORPHLINESTYLEARRAY();
|
||||
morphLineStyles.lineStyles = new MORPHLINESTYLE[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param sis
|
||||
* @param data
|
||||
* @throws IOException
|
||||
*/
|
||||
public DefineMorphShapeTag(SWFInputStream sis, ByteArrayRange data) throws IOException {
|
||||
super(sis.getSwf(), ID, "DefineMorphShape", data);
|
||||
readData(sis, data, 0, false, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException {
|
||||
characterId = sis.readUI16("characterId");
|
||||
startBounds = sis.readRECT("startBounds");
|
||||
endBounds = sis.readRECT("endBounds");
|
||||
long offset = sis.readUI32("offset"); // ignore
|
||||
morphFillStyles = sis.readMORPHFILLSTYLEARRAY("morphFillStyles");
|
||||
morphLineStyles = sis.readMORPHLINESTYLEARRAY(1, "morphLineStyles");
|
||||
startEdges = sis.readSHAPE(1, true, "startEdges");
|
||||
endEdges = sis.readSHAPE(1, true, "endEdges");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getRect(Set<BoundedTag> added) {
|
||||
RECT rect = new RECT();
|
||||
rect.Xmin = Math.min(startBounds.Xmin, endBounds.Xmin);
|
||||
rect.Ymin = Math.min(startBounds.Ymin, endBounds.Ymin);
|
||||
rect.Xmax = Math.max(startBounds.Xmax, endBounds.Xmax);
|
||||
rect.Ymax = Math.max(startBounds.Ymax, endBounds.Ymax);
|
||||
return rect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getStartBounds() {
|
||||
return startBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getEndBounds() {
|
||||
return endBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MORPHFILLSTYLEARRAY getFillStyles() {
|
||||
return morphFillStyles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MORPHLINESTYLEARRAY getLineStyles() {
|
||||
return morphLineStyles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPE getStartEdges() {
|
||||
return startEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPE getEndEdges() {
|
||||
return endEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShapeNum() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPEWITHSTYLE getShapeAtRatio(int ratio) {
|
||||
List<SHAPERECORD> finalRecords = new ArrayList<>();
|
||||
FILLSTYLEARRAY fillStyles = morphFillStyles.getFillStylesAt(ratio);
|
||||
LINESTYLEARRAY lineStyles = morphLineStyles.getLineStylesAt(getShapeNum(), ratio);
|
||||
|
||||
int startPosX = 0, startPosY = 0;
|
||||
int endPosX = 0, endPosY = 0;
|
||||
int posX = 0, posY = 0;
|
||||
|
||||
for (int startIndex = 0, endIndex = 0;
|
||||
startIndex < startEdges.shapeRecords.size()
|
||||
&& endIndex < endEdges.shapeRecords.size(); startIndex++, endIndex++) {
|
||||
|
||||
SHAPERECORD edge1 = startEdges.shapeRecords.get(startIndex);
|
||||
SHAPERECORD edge2 = endEdges.shapeRecords.get(endIndex);
|
||||
if (edge1 instanceof StyleChangeRecord || edge2 instanceof StyleChangeRecord) {
|
||||
StyleChangeRecord scr1;
|
||||
if (edge1 instanceof StyleChangeRecord) {
|
||||
scr1 = (StyleChangeRecord) edge1;
|
||||
if (scr1.stateMoveTo) {
|
||||
startPosX = scr1.moveDeltaX;
|
||||
startPosY = scr1.moveDeltaY;
|
||||
}
|
||||
} else {
|
||||
scr1 = new StyleChangeRecord();
|
||||
startIndex--;
|
||||
}
|
||||
StyleChangeRecord scr2;
|
||||
if (edge2 instanceof StyleChangeRecord) {
|
||||
scr2 = (StyleChangeRecord) edge2;
|
||||
if (scr2.stateMoveTo) {
|
||||
endPosX = scr2.moveDeltaX;
|
||||
endPosY = scr2.moveDeltaY;
|
||||
}
|
||||
} else {
|
||||
scr2 = new StyleChangeRecord();
|
||||
endIndex--;
|
||||
}
|
||||
StyleChangeRecord scr = scr1.clone();
|
||||
if (scr1.stateMoveTo || scr2.stateMoveTo) {
|
||||
scr.moveDeltaX = startPosX + (endPosX - startPosX) * ratio / MAX_RATIO;
|
||||
scr.moveDeltaY = startPosY + (endPosY - startPosY) * ratio / MAX_RATIO;
|
||||
scr.stateMoveTo = scr.moveDeltaX != posX || scr.moveDeltaY != posY;
|
||||
}
|
||||
finalRecords.add(scr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (edge1 instanceof EndShapeRecord) {
|
||||
finalRecords.add(edge1);
|
||||
break;
|
||||
}
|
||||
if (edge2 instanceof EndShapeRecord) {
|
||||
finalRecords.add(edge2);
|
||||
break;
|
||||
}
|
||||
|
||||
if (edge1 instanceof CurvedEdgeRecord || edge2 instanceof CurvedEdgeRecord) {
|
||||
CurvedEdgeRecord cer1 = null;
|
||||
if (edge1 instanceof CurvedEdgeRecord) {
|
||||
cer1 = (CurvedEdgeRecord) edge1;
|
||||
} else if (edge1 instanceof StraightEdgeRecord) {
|
||||
cer1 = SHAPERECORD.straightToCurve((StraightEdgeRecord) edge1);
|
||||
}
|
||||
CurvedEdgeRecord cer2 = null;
|
||||
if (edge2 instanceof CurvedEdgeRecord) {
|
||||
cer2 = (CurvedEdgeRecord) edge2;
|
||||
} else if (edge2 instanceof StraightEdgeRecord) {
|
||||
cer2 = SHAPERECORD.straightToCurve((StraightEdgeRecord) edge2);
|
||||
}
|
||||
if ((cer2 == null) || (cer1 == null)) {
|
||||
continue;
|
||||
}
|
||||
CurvedEdgeRecord cer = new CurvedEdgeRecord();
|
||||
cer.controlDeltaX = cer1.controlDeltaX + (cer2.controlDeltaX - cer1.controlDeltaX) * ratio / MAX_RATIO;
|
||||
cer.controlDeltaY = cer1.controlDeltaY + (cer2.controlDeltaY - cer1.controlDeltaY) * ratio / MAX_RATIO;
|
||||
cer.anchorDeltaX = cer1.anchorDeltaX + (cer2.anchorDeltaX - cer1.anchorDeltaX) * ratio / MAX_RATIO;
|
||||
cer.anchorDeltaY = cer1.anchorDeltaY + (cer2.anchorDeltaY - cer1.anchorDeltaY) * ratio / MAX_RATIO;
|
||||
startPosX += cer1.controlDeltaX + cer1.anchorDeltaX;
|
||||
startPosY += cer1.controlDeltaY + cer1.anchorDeltaY;
|
||||
endPosX += cer2.controlDeltaX + cer2.anchorDeltaX;
|
||||
endPosY += cer2.controlDeltaY + cer2.anchorDeltaY;
|
||||
posX += cer.controlDeltaX + cer.anchorDeltaX;
|
||||
posY += cer.controlDeltaY + cer.anchorDeltaY;
|
||||
finalRecords.add(cer);
|
||||
} else {
|
||||
StraightEdgeRecord ser1 = null;
|
||||
if (edge1 instanceof StraightEdgeRecord) {
|
||||
ser1 = (StraightEdgeRecord) edge1;
|
||||
}
|
||||
StraightEdgeRecord ser2 = null;
|
||||
if (edge2 instanceof StraightEdgeRecord) {
|
||||
ser2 = (StraightEdgeRecord) edge2;
|
||||
}
|
||||
if ((ser2 == null) || (ser1 == null)) {
|
||||
continue;
|
||||
}
|
||||
StraightEdgeRecord ser = new StraightEdgeRecord();
|
||||
ser.generalLineFlag = true;
|
||||
ser.vertLineFlag = false;
|
||||
ser.deltaX = ser1.deltaX + (ser2.deltaX - ser1.deltaX) * ratio / MAX_RATIO;
|
||||
ser.deltaY = ser1.deltaY + (ser2.deltaY - ser1.deltaY) * ratio / MAX_RATIO;
|
||||
startPosX += ser1.deltaX;
|
||||
startPosY += ser1.deltaY;
|
||||
endPosX += ser2.deltaX;
|
||||
endPosY += ser2.deltaY;
|
||||
posX += ser.deltaX;
|
||||
posY += ser.deltaX;
|
||||
finalRecords.add(ser);
|
||||
}
|
||||
}
|
||||
SHAPEWITHSTYLE shape = new SHAPEWITHSTYLE();
|
||||
shape.fillStyles = fillStyles;
|
||||
shape.lineStyles = lineStyles;
|
||||
shape.shapeRecords = finalRecords;
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, Matrix transformation, ColorTransform colorTransform) {
|
||||
SHAPEWITHSTYLE shape = getShapeAtRatio(ratio);
|
||||
// shapeNum: 3
|
||||
// todo: Currently the generated image is not cached, because the cache
|
||||
// key contains the hashCode of the finalRecord object, and it is always
|
||||
// recreated
|
||||
BitmapExporter.export(swf, shape, null, image, transformation, colorTransform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, double zoom) {
|
||||
if (ratio == -2) {
|
||||
SHAPEWITHSTYLE beginShapes = getShapeAtRatio(0);
|
||||
SHAPEWITHSTYLE endShapes = getShapeAtRatio(65535);
|
||||
SVGMorphShapeExporter shapeExporter = new SVGMorphShapeExporter(swf, beginShapes, endShapes, exporter, null, colorTransform, zoom);
|
||||
shapeExporter.export();
|
||||
} else {
|
||||
SHAPEWITHSTYLE shapes = getShapeAtRatio(ratio);
|
||||
SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shapes, exporter, null, colorTransform, zoom);
|
||||
shapeExporter.export();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumFrames() {
|
||||
return 65536;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleFrame() {
|
||||
// Morpshape is a single frame specified with the ratio
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
|
||||
return transformation.toTransform().createTransformedShape(getShapeAtRatio(ratio).getOutline());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtmlCanvas(double unitDivisor) {
|
||||
CanvasMorphShapeExporter cmse = new CanvasMorphShapeExporter(swf, getShapeAtRatio(0), getShapeAtRatio(MAX_RATIO), new ColorTransform(), unitDivisor, 0, 0);
|
||||
cmse.export();
|
||||
|
||||
return cmse.getShapeData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterId(int characterId) {
|
||||
this.characterId = characterId;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2010-2015 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;
|
||||
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.SWFInputStream;
|
||||
import com.jpexs.decompiler.flash.SWFOutputStream;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.CanvasMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.morphshape.SVGMorphShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter;
|
||||
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.MorphShapeTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
import com.jpexs.decompiler.flash.types.BasicType;
|
||||
import com.jpexs.decompiler.flash.types.ColorTransform;
|
||||
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.MORPHLINESTYLE;
|
||||
import com.jpexs.decompiler.flash.types.MORPHLINESTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.RECT;
|
||||
import com.jpexs.decompiler.flash.types.SHAPE;
|
||||
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.annotations.SWFType;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
|
||||
import com.jpexs.helpers.ByteArrayRange;
|
||||
import com.jpexs.helpers.SerializableImage;
|
||||
import java.awt.Shape;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class DefineMorphShapeTag extends MorphShapeTag {
|
||||
|
||||
@SWFType(BasicType.UI16)
|
||||
public int characterId;
|
||||
|
||||
public RECT startBounds;
|
||||
|
||||
public RECT endBounds;
|
||||
|
||||
public MORPHFILLSTYLEARRAY morphFillStyles;
|
||||
|
||||
public MORPHLINESTYLEARRAY morphLineStyles;
|
||||
|
||||
public SHAPE startEdges;
|
||||
|
||||
public SHAPE endEdges;
|
||||
|
||||
public static final int ID = 46;
|
||||
|
||||
public static final int MAX_RATIO = 65535;
|
||||
|
||||
@Override
|
||||
public void getNeededCharacters(Set<Integer> needed) {
|
||||
morphFillStyles.getNeededCharacters(needed);
|
||||
startEdges.getNeededCharacters(needed);
|
||||
endEdges.getNeededCharacters(needed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceCharacter(int oldCharacterId, int newCharacterId) {
|
||||
boolean modified = false;
|
||||
modified |= morphFillStyles.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
modified |= startEdges.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
modified |= endEdges.replaceCharacter(oldCharacterId, newCharacterId);
|
||||
if (modified) {
|
||||
setModified(true);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeCharacter(int characterId) {
|
||||
boolean modified = false;
|
||||
modified |= morphFillStyles.removeCharacter(characterId);
|
||||
modified |= startEdges.removeCharacter(characterId);
|
||||
modified |= endEdges.removeCharacter(characterId);
|
||||
if (modified) {
|
||||
setModified(true);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharacterId() {
|
||||
return characterId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data bytes
|
||||
*
|
||||
* @return Bytes of data
|
||||
*/
|
||||
@Override
|
||||
public byte[] getData() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
OutputStream os = baos;
|
||||
SWFOutputStream sos = new SWFOutputStream(os, getVersion());
|
||||
try {
|
||||
sos.writeUI16(characterId);
|
||||
sos.writeRECT(startBounds);
|
||||
sos.writeRECT(endBounds);
|
||||
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
||||
SWFOutputStream sos2 = new SWFOutputStream(baos2, getVersion());
|
||||
sos2.writeMORPHFILLSTYLEARRAY(morphFillStyles, 1);
|
||||
sos2.writeMORPHLINESTYLEARRAY(morphLineStyles, 1);
|
||||
sos2.writeSHAPE(startEdges, 1);
|
||||
byte[] d = baos2.toByteArray();
|
||||
sos.writeUI32(d.length);
|
||||
sos.write(d);
|
||||
sos.writeSHAPE(endEdges, 1);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new Error("This should never happen.", e);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param swf
|
||||
*/
|
||||
public DefineMorphShapeTag(SWF swf) {
|
||||
super(swf, ID, "DefineMorphShape", null);
|
||||
characterId = swf.getNextCharacterId();
|
||||
startBounds = new RECT();
|
||||
endBounds = new RECT();
|
||||
startEdges = SHAPE.createEmpty(1);
|
||||
endEdges = SHAPE.createEmpty(1);
|
||||
morphFillStyles = new MORPHFILLSTYLEARRAY();
|
||||
morphFillStyles.fillStyles = new MORPHFILLSTYLE[0];
|
||||
morphLineStyles = new MORPHLINESTYLEARRAY();
|
||||
morphLineStyles.lineStyles = new MORPHLINESTYLE[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param sis
|
||||
* @param data
|
||||
* @throws IOException
|
||||
*/
|
||||
public DefineMorphShapeTag(SWFInputStream sis, ByteArrayRange data) throws IOException {
|
||||
super(sis.getSwf(), ID, "DefineMorphShape", data);
|
||||
readData(sis, data, 0, false, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException {
|
||||
characterId = sis.readUI16("characterId");
|
||||
startBounds = sis.readRECT("startBounds");
|
||||
endBounds = sis.readRECT("endBounds");
|
||||
long offset = sis.readUI32("offset"); // ignore
|
||||
morphFillStyles = sis.readMORPHFILLSTYLEARRAY("morphFillStyles");
|
||||
morphLineStyles = sis.readMORPHLINESTYLEARRAY(1, "morphLineStyles");
|
||||
startEdges = sis.readSHAPE(1, true, "startEdges");
|
||||
endEdges = sis.readSHAPE(1, true, "endEdges");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getRect(Set<BoundedTag> added) {
|
||||
RECT rect = new RECT();
|
||||
rect.Xmin = Math.min(startBounds.Xmin, endBounds.Xmin);
|
||||
rect.Ymin = Math.min(startBounds.Ymin, endBounds.Ymin);
|
||||
rect.Xmax = Math.max(startBounds.Xmax, endBounds.Xmax);
|
||||
rect.Ymax = Math.max(startBounds.Ymax, endBounds.Ymax);
|
||||
return rect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getStartBounds() {
|
||||
return startBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RECT getEndBounds() {
|
||||
return endBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MORPHFILLSTYLEARRAY getFillStyles() {
|
||||
return morphFillStyles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MORPHLINESTYLEARRAY getLineStyles() {
|
||||
return morphLineStyles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPE getStartEdges() {
|
||||
return startEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPE getEndEdges() {
|
||||
return endEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShapeNum() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SHAPEWITHSTYLE getShapeAtRatio(int ratio) {
|
||||
List<SHAPERECORD> finalRecords = new ArrayList<>();
|
||||
FILLSTYLEARRAY fillStyles = morphFillStyles.getFillStylesAt(ratio);
|
||||
LINESTYLEARRAY lineStyles = morphLineStyles.getLineStylesAt(getShapeNum(), ratio);
|
||||
|
||||
int startPosX = 0, startPosY = 0;
|
||||
int endPosX = 0, endPosY = 0;
|
||||
int posX = 0, posY = 0;
|
||||
|
||||
for (int startIndex = 0, endIndex = 0;
|
||||
startIndex < startEdges.shapeRecords.size()
|
||||
&& endIndex < endEdges.shapeRecords.size(); startIndex++, endIndex++) {
|
||||
|
||||
SHAPERECORD edge1 = startEdges.shapeRecords.get(startIndex);
|
||||
SHAPERECORD edge2 = endEdges.shapeRecords.get(endIndex);
|
||||
if (edge1 instanceof StyleChangeRecord || edge2 instanceof StyleChangeRecord) {
|
||||
StyleChangeRecord scr1;
|
||||
if (edge1 instanceof StyleChangeRecord) {
|
||||
scr1 = (StyleChangeRecord) edge1;
|
||||
if (scr1.stateMoveTo) {
|
||||
startPosX = scr1.moveDeltaX;
|
||||
startPosY = scr1.moveDeltaY;
|
||||
}
|
||||
} else {
|
||||
scr1 = new StyleChangeRecord();
|
||||
startIndex--;
|
||||
}
|
||||
StyleChangeRecord scr2;
|
||||
if (edge2 instanceof StyleChangeRecord) {
|
||||
scr2 = (StyleChangeRecord) edge2;
|
||||
if (scr2.stateMoveTo) {
|
||||
endPosX = scr2.moveDeltaX;
|
||||
endPosY = scr2.moveDeltaY;
|
||||
}
|
||||
} else {
|
||||
scr2 = new StyleChangeRecord();
|
||||
endIndex--;
|
||||
}
|
||||
StyleChangeRecord scr = scr1.clone();
|
||||
if (scr1.stateMoveTo || scr2.stateMoveTo) {
|
||||
scr.moveDeltaX = startPosX + (endPosX - startPosX) * ratio / MAX_RATIO;
|
||||
scr.moveDeltaY = startPosY + (endPosY - startPosY) * ratio / MAX_RATIO;
|
||||
scr.stateMoveTo = scr.moveDeltaX != posX || scr.moveDeltaY != posY;
|
||||
}
|
||||
finalRecords.add(scr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (edge1 instanceof EndShapeRecord) {
|
||||
finalRecords.add(edge1);
|
||||
break;
|
||||
}
|
||||
if (edge2 instanceof EndShapeRecord) {
|
||||
finalRecords.add(edge2);
|
||||
break;
|
||||
}
|
||||
|
||||
if (edge1 instanceof CurvedEdgeRecord || edge2 instanceof CurvedEdgeRecord) {
|
||||
CurvedEdgeRecord cer1 = null;
|
||||
if (edge1 instanceof CurvedEdgeRecord) {
|
||||
cer1 = (CurvedEdgeRecord) edge1;
|
||||
} else if (edge1 instanceof StraightEdgeRecord) {
|
||||
cer1 = SHAPERECORD.straightToCurve((StraightEdgeRecord) edge1);
|
||||
}
|
||||
CurvedEdgeRecord cer2 = null;
|
||||
if (edge2 instanceof CurvedEdgeRecord) {
|
||||
cer2 = (CurvedEdgeRecord) edge2;
|
||||
} else if (edge2 instanceof StraightEdgeRecord) {
|
||||
cer2 = SHAPERECORD.straightToCurve((StraightEdgeRecord) edge2);
|
||||
}
|
||||
if ((cer2 == null) || (cer1 == null)) {
|
||||
continue;
|
||||
}
|
||||
CurvedEdgeRecord cer = new CurvedEdgeRecord();
|
||||
cer.controlDeltaX = cer1.controlDeltaX + (cer2.controlDeltaX - cer1.controlDeltaX) * ratio / MAX_RATIO;
|
||||
cer.controlDeltaY = cer1.controlDeltaY + (cer2.controlDeltaY - cer1.controlDeltaY) * ratio / MAX_RATIO;
|
||||
cer.anchorDeltaX = cer1.anchorDeltaX + (cer2.anchorDeltaX - cer1.anchorDeltaX) * ratio / MAX_RATIO;
|
||||
cer.anchorDeltaY = cer1.anchorDeltaY + (cer2.anchorDeltaY - cer1.anchorDeltaY) * ratio / MAX_RATIO;
|
||||
startPosX += cer1.controlDeltaX + cer1.anchorDeltaX;
|
||||
startPosY += cer1.controlDeltaY + cer1.anchorDeltaY;
|
||||
endPosX += cer2.controlDeltaX + cer2.anchorDeltaX;
|
||||
endPosY += cer2.controlDeltaY + cer2.anchorDeltaY;
|
||||
posX += cer.controlDeltaX + cer.anchorDeltaX;
|
||||
posY += cer.controlDeltaY + cer.anchorDeltaY;
|
||||
finalRecords.add(cer);
|
||||
} else {
|
||||
StraightEdgeRecord ser1 = null;
|
||||
if (edge1 instanceof StraightEdgeRecord) {
|
||||
ser1 = (StraightEdgeRecord) edge1;
|
||||
}
|
||||
StraightEdgeRecord ser2 = null;
|
||||
if (edge2 instanceof StraightEdgeRecord) {
|
||||
ser2 = (StraightEdgeRecord) edge2;
|
||||
}
|
||||
if ((ser2 == null) || (ser1 == null)) {
|
||||
continue;
|
||||
}
|
||||
StraightEdgeRecord ser = new StraightEdgeRecord();
|
||||
ser.generalLineFlag = true;
|
||||
ser.vertLineFlag = false;
|
||||
ser.deltaX = ser1.deltaX + (ser2.deltaX - ser1.deltaX) * ratio / MAX_RATIO;
|
||||
ser.deltaY = ser1.deltaY + (ser2.deltaY - ser1.deltaY) * ratio / MAX_RATIO;
|
||||
startPosX += ser1.deltaX;
|
||||
startPosY += ser1.deltaY;
|
||||
endPosX += ser2.deltaX;
|
||||
endPosY += ser2.deltaY;
|
||||
posX += ser.deltaX;
|
||||
posY += ser.deltaX;
|
||||
finalRecords.add(ser);
|
||||
}
|
||||
}
|
||||
SHAPEWITHSTYLE shape = new SHAPEWITHSTYLE();
|
||||
shape.fillStyles = fillStyles;
|
||||
shape.lineStyles = lineStyles;
|
||||
shape.shapeRecords = finalRecords;
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, Matrix transformation, ColorTransform colorTransform) {
|
||||
SHAPEWITHSTYLE shape = getShapeAtRatio(ratio);
|
||||
// shapeNum: 3
|
||||
// todo: Currently the generated image is not cached, because the cache
|
||||
// key contains the hashCode of the finalRecord object, and it is always
|
||||
// recreated
|
||||
BitmapExporter.export(swf, shape, null, image, transformation, colorTransform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, double zoom) {
|
||||
if (ratio == -2) {
|
||||
SHAPEWITHSTYLE beginShapes = getShapeAtRatio(0);
|
||||
SHAPEWITHSTYLE endShapes = getShapeAtRatio(65535);
|
||||
SVGMorphShapeExporter shapeExporter = new SVGMorphShapeExporter(swf, beginShapes, endShapes, exporter, null, colorTransform, zoom);
|
||||
shapeExporter.export();
|
||||
} else {
|
||||
SHAPEWITHSTYLE shapes = getShapeAtRatio(ratio);
|
||||
SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shapes, exporter, null, colorTransform, zoom);
|
||||
shapeExporter.export();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumFrames() {
|
||||
return 65536;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleFrame() {
|
||||
// Morpshape is a single frame specified with the ratio
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
|
||||
return transformation.toTransform().createTransformedShape(getShapeAtRatio(ratio).getOutline());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtmlCanvas(double unitDivisor) {
|
||||
CanvasMorphShapeExporter cmse = new CanvasMorphShapeExporter(swf, getShapeAtRatio(0), getShapeAtRatio(MAX_RATIO), new ColorTransform(), unitDivisor, 0, 0);
|
||||
cmse.export();
|
||||
return cmse.getShapeData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterId(int characterId) {
|
||||
this.characterId = characterId;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user