/* * Copyright (C) 2010-2016 JPEXS, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.jpexs.decompiler.flash.tags.base; import com.jpexs.decompiler.flash.SWF; 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.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.MORPHFILLSTYLEARRAY; 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.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * * @author JPEXS */ public abstract class MorphShapeTag extends DrawableTag { public static final int MAX_RATIO = 65535; @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 MorphShapeTag(SWF swf, int id, String name, ByteArrayRange data) { super(swf, id, name, data); } public abstract int getShapeNum(); @Override public RECT getRect() { return getRect(new HashSet<>()); } @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; } @Override public void setCharacterId(int characterId) { this.characterId = characterId; } @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; } public RECT getStartBounds() { return startBounds; } public RECT getEndBounds() { return endBounds; } public MORPHFILLSTYLEARRAY getFillStyles() { return morphFillStyles; } public MORPHLINESTYLEARRAY getLineStyles() { return morphLineStyles; } public SHAPE getStartEdges() { return startEdges; } public SHAPE getEndEdges() { return endEdges; } 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 int getUsedParameters() { return PARAMETER_RATIO; } @Override public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { SHAPEWITHSTYLE shape = getShapeAtRatio(ratio); // morphShape using shapeNum=3, morphShape2 using 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, strokeTransformation, colorTransform); } @Override public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) { if (ratio == -2) { SHAPEWITHSTYLE beginShapes = getShapeAtRatio(0); SHAPEWITHSTYLE endShapes = getShapeAtRatio(65535); SVGMorphShapeExporter shapeExporter = new SVGMorphShapeExporter(swf, beginShapes, endShapes, exporter, null, colorTransform, 1); shapeExporter.export(); } else { SHAPEWITHSTYLE shapes = getShapeAtRatio(ratio); SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shapes, exporter, null, colorTransform, 1); 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, boolean stroked) { return transformation.toTransform().createTransformedShape(getShapeAtRatio(ratio).getOutline(swf, stroked)); } @Override public void toHtmlCanvas(StringBuilder result, double unitDivisor) { CanvasMorphShapeExporter cmse = new CanvasMorphShapeExporter(swf, getShapeAtRatio(0), getShapeAtRatio(MAX_RATIO), null, unitDivisor, 0, 0); cmse.export(); result.append(cmse.getShapeData()); } }