/* * 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; import com.jpexs.decompiler.flash.ReadOnlyTagList; 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.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.DrawableTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RenderContext; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.ColorTransform; import com.jpexs.decompiler.flash.types.MATRIX; import com.jpexs.decompiler.flash.types.RECT; import com.jpexs.decompiler.flash.types.annotations.Internal; import com.jpexs.decompiler.flash.types.annotations.SWFField; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Cache; import com.jpexs.helpers.SerializableImage; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Defines a sprite character * * @author JPEXS */ @SWFVersion(from = 3) public class DefineSpriteTag extends DrawableTag implements Timelined { public static final int ID = 39; public static final String NAME = "DefineSprite"; /** * Character ID of sprite */ @SWFType(BasicType.UI16) public int spriteId; /** * Number of frames in sprite */ @SWFType(BasicType.UI16) public int frameCount; /** * A series of tags */ @SWFField private List<Tag> subTags; @Internal public ReadOnlyTagList readOnlyTags; public boolean hasEndTag; private Timeline timeline; private boolean isSingleFrameInitialized; private boolean isSingleFrame; /** * Constructor * * @param swf */ public DefineSpriteTag(SWF swf) { super(swf, ID, NAME, null); spriteId = swf.getNextCharacterId(); subTags = new ArrayList<>(); } /** * Constructor * * @param sis * @param data * @param level * @param parallel * @param skipUnusualTags * @throws IOException * @throws java.lang.InterruptedException */ public DefineSpriteTag(SWFInputStream sis, int level, ByteArrayRange data, boolean parallel, boolean skipUnusualTags) throws IOException, InterruptedException { super(sis.getSwf(), ID, NAME, data); readData(sis, data, level, parallel, skipUnusualTags, false); } @Override public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException, InterruptedException { spriteId = sis.readUI16("spriteId"); frameCount = sis.readUI16("frameCount"); List<Tag> subTags = sis.readTagList(this, level + 1, parallel, skipUnusualTags, true, lazy); if (subTags.size() > 0 && subTags.get(subTags.size() - 1).getId() == EndTag.ID) { hasEndTag = true; subTags.remove(subTags.size() - 1); } this.subTags = subTags; readOnlyTags = null; } /** * Gets data bytes * * @param sos SWF output stream * @throws java.io.IOException */ @Override public void getData(SWFOutputStream sos) throws IOException { sos.writeUI16(spriteId); sos.writeUI16(frameCount); sos.writeTags(getTags()); if (hasEndTag) { sos.writeUI16(0); } } @Override public Timeline getTimeline() { if (timeline == null) { timeline = new Timeline(swf, this, spriteId, getRect()); } return timeline; } @Override public void resetTimeline() { if (timeline != null) { timeline.reset(swf, this, spriteId, getRect()); } } @Override public int getCharacterId() { return spriteId; } @Override public void setCharacterId(int characterId) { this.spriteId = characterId; } private RECT getCharacterBounds(Set<Integer> characters, Set<BoundedTag> added) { RECT ret = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE); boolean foundSomething = false; for (int c : characters) { Tag t = swf.getCharacter(c); RECT r = null; if (t instanceof BoundedTag) { BoundedTag bt = (BoundedTag) t; if (!added.contains(bt)) { added.add(bt); r = bt.getRect(added); added.remove(bt); } } if (r != null) { if (r.Xmin < r.Xmax && r.Ymin < r.Ymax) { foundSomething = true; ret.Xmin = Math.min(r.Xmin, ret.Xmin); ret.Ymin = Math.min(r.Ymin, ret.Ymin); ret.Xmax = Math.max(r.Xmax, ret.Xmax); ret.Ymax = Math.max(r.Ymax, ret.Ymax); } } } if (!foundSomething) { return new RECT(); } return ret; } @Override public RECT getRect() { return getRect(new HashSet<>()); } @Override public RECT getRect(Set<BoundedTag> added) { Cache<CharacterTag, RECT> cache = swf == null ? null : swf.getRectCache(); RECT ret = cache == null ? null : cache.get(this); if (ret != null) { return ret; } ret = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE); HashMap<Integer, Integer> depthMap = new HashMap<>(); boolean foundSomething = false; for (Tag t : getTags()) { MATRIX m = null; int characterId = -1; if (t instanceof PlaceObjectTypeTag) { PlaceObjectTypeTag pot = (PlaceObjectTypeTag) t; m = pot.getMatrix(); int charId = pot.getCharacterId(); if (charId > -1) { depthMap.put(pot.getDepth(), charId); characterId = charId; } else { Integer chi = depthMap.get(pot.getDepth()); if (chi != null) { characterId = chi; } } } if (characterId == -1) { continue; } Set<Integer> need = new HashSet<>(); need.add(characterId); RECT r = getCharacterBounds(need, added); if (m != null) { AffineTransform trans = SWF.matrixToTransform(m); java.awt.Point topleft = new java.awt.Point(); trans.transform(new java.awt.Point(r.Xmin, r.Ymin), topleft); java.awt.Point topright = new java.awt.Point(); trans.transform(new java.awt.Point(r.Xmax, r.Ymin), topright); java.awt.Point bottomright = new java.awt.Point(); trans.transform(new java.awt.Point(r.Xmax, r.Ymax), bottomright); java.awt.Point bottomleft = new java.awt.Point(); trans.transform(new java.awt.Point(r.Xmin, r.Ymax), bottomleft); r.Xmin = (int) Math.min(Math.min(Math.min(topleft.x, topright.x), bottomleft.x), bottomright.x); r.Ymin = (int) Math.min(Math.min(Math.min(topleft.y, topright.y), bottomleft.y), bottomright.y); r.Xmax = (int) Math.max(Math.max(Math.max(topleft.x, topright.x), bottomleft.x), bottomright.x); r.Ymax = (int) Math.max(Math.max(Math.max(topleft.y, topright.y), bottomleft.y), bottomright.y); } ret.Xmin = Math.min(r.Xmin, ret.Xmin); ret.Ymin = Math.min(r.Ymin, ret.Ymin); ret.Xmax = Math.max(r.Xmax, ret.Xmax); ret.Ymax = Math.max(r.Ymax, ret.Ymax); foundSomething = true; } if (!foundSomething) { ret = new RECT(); } if (cache != null) { cache.put(this, ret); } return ret; } @Override public void setModified(boolean value) { if (!value) { for (Tag subTag : getTags()) { subTag.setModified(false); } } super.setModified(value); } @Override public ReadOnlyTagList getTags() { if (readOnlyTags == null) { readOnlyTags = new ReadOnlyTagList(subTags); } return readOnlyTags; } @Override public void removeTag(int index) { setModified(true); subTags.remove(index); } @Override public void removeTag(Tag tag) { setModified(true); subTags.remove(tag); } @Override public void addTag(Tag tag) { setModified(true); subTags.add(tag); } @Override public void addTag(int index, Tag tag) { setModified(true); subTags.add(index, tag); } @Override public void createOriginalData() { super.createOriginalData(); for (Tag subTag : getTags()) { subTag.createOriginalData(); } } @Override public void getNeededCharacters(Set<Integer> needed) { for (Tag t : getTags()) { if (t instanceof CharacterIdTag) { needed.add(((CharacterIdTag) t).getCharacterId()); } } } @Override public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { boolean modified = getTimeline().replaceCharacter(oldCharacterId, newCharacterId); if (modified) { setModified(true); } return modified; } @Override public boolean removeCharacter(int characterId) { boolean modified = getTimeline().removeCharacter(characterId); if (modified) { setModified(true); } return modified; } @Override public int getUsedParameters() { return PARAMETER_FRAME | PARAMETER_TIME | PARAMETER_RATIO; // inner tags can contain morphshapes, too } @Override public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) { return getTimeline().getOutline(frame, time, renderContext, transformation, stroked); } @Override public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { getTimeline().toImage(frame, time, renderContext, image, isClip, transformation, strokeTransformation, absoluteTransformation, colorTransform); } @Override public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException { getTimeline().toSVG(0, 0, null, 0, exporter, colorTransform, level + 1); } @Override public void toHtmlCanvas(StringBuilder result, double unitDivisor) { getTimeline().toHtmlCanvas(result, unitDivisor, null); } @Override public int getNumFrames() { // flashplayer ignores the count stored in frameCount return getTimeline().getFrameCount(); // frameCount } @Override public boolean isSingleFrame() { if (!isSingleFrameInitialized) { initialiteIsSingleFrame(); } return isSingleFrame; } private synchronized void initialiteIsSingleFrame() { if (!isSingleFrameInitialized) { if (getTimeline().getRealFrameCount() > 1) { isSingleFrameInitialized = true; return; } isSingleFrame = getTimeline().isSingleFrame(); isSingleFrameInitialized = true; } } @Override public boolean isModified() { if (super.isModified()) { return true; } for (Tag t : getTags()) { if (t.isModified()) { return true; } } return false; } public void clearReadOnlyListCache() { readOnlyTags = null; } @Override public void replaceTag(int index, Tag newTag) { removeTag(index); addTag(index, newTag); } }