From 72426183d8ccdb74bf8745815716fc831c45ca4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Tue, 2 Mar 2021 22:23:35 +0100 Subject: [PATCH] PlaceObject tags matrix editation - FreeTransform tool (move, resize, rotate, shear) --- CHANGELOG.md | 1 + .../decompiler/flash/timeline/DepthState.java | 8 +- .../decompiler/flash/gui/ImagePanel.java | 899 +++++++++++++++++- .../jpexs/decompiler/flash/gui/MainPanel.java | 8 + .../decompiler/flash/gui/PreviewPanel.java | 108 +++ .../flash/gui/graphics/cursors/move.png | Bin 0 -> 731 bytes .../gui/graphics/cursors/move_regpoint.png | Bin 0 -> 6004 bytes .../gui/graphics/cursors/resize_nw_se.png | Bin 0 -> 5467 bytes .../gui/graphics/cursors/resize_sw_ne.png | Bin 0 -> 620 bytes .../flash/gui/graphics/cursors/resize_x.png | Bin 0 -> 5434 bytes .../flash/gui/graphics/cursors/resize_y.png | Bin 0 -> 6144 bytes .../flash/gui/graphics/cursors/rotate.png | Bin 0 -> 6001 bytes .../flash/gui/graphics/cursors/select.png | Bin 0 -> 6473 bytes .../flash/gui/graphics/cursors/shear_x.png | Bin 0 -> 5438 bytes .../flash/gui/graphics/cursors/shear_y.png | Bin 0 -> 5696 bytes 15 files changed, 1003 insertions(+), 21 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/move.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/move_regpoint.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/resize_nw_se.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/resize_sw_ne.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/resize_x.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/resize_y.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/rotate.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/select.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/shear_x.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cursors/shear_y.png diff --git a/CHANGELOG.md b/CHANGELOG.md index c51ee2108..ab94bf09a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ### Added - #1561 Font editing - import ascent, descent, leading, kerning - Font editing - font name, ascent, descent, leading +- PlaceObject tags matrix editation - FreeTransform tool (move, resize, rotate, shear) ### Fixed - #1623 Right side marker (gray line) in P-code diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/DepthState.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/DepthState.java index 0ee40d595..5d81282de 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/DepthState.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/DepthState.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.timeline; import com.jpexs.decompiler.flash.SWF; @@ -118,6 +119,11 @@ public class DepthState { } } + public void setMATRIX(MATRIX matrix) { + this.matrix = matrix; + this.placeObjectTag.setMatrix(matrix); + } + public boolean cacheAsBitmap() { return (placeObjectTag != null && placeObjectTag.cacheAsBitmap()) || (filters != null && filters.size() > 0); diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index b2a246058..358f33873 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -42,10 +42,12 @@ import com.jpexs.decompiler.flash.timeline.Frame; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.types.ConstantColorColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; import com.jpexs.decompiler.flash.types.RECT; import com.jpexs.decompiler.flash.types.SOUNDINFO; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Cache; +import com.jpexs.helpers.Reference; import com.jpexs.helpers.SerializableImage; import com.jpexs.helpers.Stopwatch; import java.awt.AlphaComposite; @@ -55,9 +57,12 @@ import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; +import java.awt.Stroke; +import java.awt.Toolkit; import java.awt.Transparency; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; @@ -67,6 +72,9 @@ import java.awt.event.MouseListener; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionListener; import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import java.io.IOException; @@ -78,10 +86,12 @@ import java.util.Timer; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; +import javax.imageio.ImageIO; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.event.MouseInputAdapter; /** * @@ -129,6 +139,8 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private int selectedDepth = -1; + private int freeTransformDepth = -1; + private Zoom zoom = new Zoom(); private final Object delayObject = new Object(); @@ -145,16 +157,153 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private final boolean lowQuality = false; + private Object lock = new Object(); + + private Point2D registrationPoint = null; + private Point2D registrationPointUpdated = null; + + private int mode = Cursor.DEFAULT_CURSOR; + private Rectangle2D bounds; + + private Matrix transform; + private AffineTransform transformUpdated; + private final double LQ_FACTOR = 2; + private static final int TOLERANCE_SCALESHEAR = 8; + + private static final int TOLERANCE_ROTATE = 30; + private static final int REGISTRATION_TOLERANCE = 8; + + private static final double CENTER_POINT_SIZE = 8; + + private static final double HANDLES_WIDTH = 5; + + private static final int HANDLES_STROKE_WIDTH = 2; + + private static final int MODE_ROTATE_NE = -1; + private static final int MODE_ROTATE_SE = -2; + private static final int MODE_ROTATE_NW = -3; + private static final int MODE_ROTATE_SW = -4; + + private static final int MODE_SHEAR_S = -5; + private static final int MODE_SHEAR_E = -6; + + private static final int MODE_SHEAR_N = -7; + private static final int MODE_SHEAR_W = -8; + + private static Cursor moveCursor; + private static Cursor moveRegPointCursor; + private static Cursor resizeNWSECursor; + private static Cursor resizeSWNECursor; + private static Cursor resizeXCursor; + private static Cursor resizeYCursor; + private static Cursor rotateCursor; + private static Cursor selectCursor; + private static Cursor shearXCursor; + private static Cursor shearYCursor; + + private static Cursor loadCursor(String name, int x, int y) throws IOException { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Image image = ImageIO.read(MainPanel.class.getResource("/com/jpexs/decompiler/flash/gui/graphics/cursors/" + name + ".png")); + return toolkit.createCustomCursor(image, new Point(x, y), name); + } + + static { + try { + moveCursor = loadCursor("move", 0, 0); + moveRegPointCursor = loadCursor("move_regpoint", 0, 0); + resizeNWSECursor = loadCursor("resize_nw_se", 5, 5); + resizeSWNECursor = loadCursor("resize_sw_ne", 5, 5); + resizeXCursor = loadCursor("resize_x", 7, 4); + resizeYCursor = loadCursor("resize_y", 4, 7); + rotateCursor = loadCursor("rotate", 10, 7); + selectCursor = loadCursor("select", 0, 0); + shearXCursor = loadCursor("shear_x", 9, 5); + shearYCursor = loadCursor("shear_y", 5, 9); + + } catch (IOException ex) { + Logger.getLogger(MainPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public MATRIX getNewMatrix() { + DepthState ds = null; + Timeline timeline = timelined.getTimeline(); + if (freeTransformDepth > -1 && timeline.getFrameCount() > frame) { + ds = timeline.getFrame(frame).layers.get(freeTransformDepth); + } + + if (ds != null) { + CharacterTag cht = swf.getCharacter(ds.characterId); + if (cht != null) { + if (cht instanceof DrawableTag) { + RECT rect = timelined.getRect(); + Matrix m = new Matrix(); + double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; + if (lowQuality) { + zoomDouble /= LQ_FACTOR; + } + double zoom = zoomDouble; + m.translate(-rect.Xmin * zoom, -rect.Ymin * zoom); + m.scale(zoom); + + Matrix eMatrix = Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m).inverse(); + + return transform.preConcatenate(eMatrix).toMATRIX(); + } + } + } + return null; + } + public synchronized void selectDepth(int depth) { if (depth != selectedDepth) { this.selectedDepth = depth; + freeTransformDepth = -1; } hideMouseSelection(); } + public synchronized void freeTransformDepth(int depth) { + if (depth != freeTransformDepth) { + this.freeTransformDepth = depth; + } + registrationPoint = null; + + DepthState ds = null; + Timeline timeline = timelined.getTimeline(); + if (freeTransformDepth > -1 && timeline.getFrameCount() > frame) { + ds = timeline.getFrame(frame).layers.get(freeTransformDepth); + } + + if (ds != null) { + CharacterTag cht = swf.getCharacter(ds.characterId); + if (cht != null) { + if (cht instanceof DrawableTag) { + DrawableTag dt = (DrawableTag) cht; + int drawableFrameCount = dt.getNumFrames(); + if (drawableFrameCount == 0) { + drawableFrameCount = 1; + } + RECT rect = timelined.getRect(); + Matrix m = new Matrix(); + double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; + if (lowQuality) { + zoomDouble /= LQ_FACTOR; + } + double zoom = zoomDouble; + m.translate(-rect.Xmin * zoom, -rect.Ymin * zoom); + m.scale(zoom); + + transform = Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m).concatenate(new Matrix(ds.matrix)); + } + } + } + hideMouseSelection(); + } + public void fireMediaDisplayStateChanged() { for (MediaDisplayListener l : listeners) { l.mediaDisplayStateChanged(this); @@ -268,7 +417,8 @@ public final class ImagePanel extends JPanel implements MediaDisplay { repaint(); } }); - addMouseListener(new MouseAdapter() { + + MouseInputAdapter mouseInputAdapter = new MouseInputAdapter() { @Override public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { @@ -280,13 +430,64 @@ public final class ImagePanel extends JPanel implements MediaDisplay { public void mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { dragStart = null; + + if (freeTransformDepth > -1 && mode != Cursor.DEFAULT_CURSOR && registrationPointUpdated != null && transformUpdated != null) { + synchronized (lock) { + registrationPoint = new Point2D.Double(registrationPointUpdated.getX(), registrationPointUpdated.getY()); + transform = new Matrix(transformUpdated); + transformUpdated = null; + + /*DepthState ds = timelined.getTimeline().getFrame(frame).layers.get(freeTransformDepth); + CharacterTag cht = swf.getCharacter(ds.characterId); + + DrawableTag dt = (DrawableTag) cht; + int drawableFrameCount = dt.getNumFrames(); + if (drawableFrameCount == 0) { + drawableFrameCount = 1; + } + + RenderContext renderContext = new RenderContext(); + renderContext.displayObjectCache = displayObjectCache; + if (cursorPosition != null) { + renderContext.cursorPosition = new Point((int) (cursorPosition.x * SWF.unitDivisor), (int) (cursorPosition.y * SWF.unitDivisor)); + } + + renderContext.mouseButton = mouseButton; + renderContext.stateUnderCursor = new ArrayList<>(); + + int dframe = time % drawableFrameCount; + RECT rect = timelined.getRect(); + Matrix m = new Matrix(); + double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; + if (lowQuality) { + zoomDouble /= LQ_FACTOR; + } + double zoom = zoomDouble; + m.translate(-rect.Xmin * zoom, -rect.Ymin * zoom); + m.scale(zoom); + + Matrix eMatrix = Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m).inverse(); + + MATRIX oldMatrix = ds.matrix; + ds.matrix = transform.preConcatenate(eMatrix).toMATRIX(); + // Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m.concatenate(new Matrix(ds.matrix))) + Matrix outlineMatrix = Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m.concatenate(new Matrix( + //ds.matrix + transform.preConcatenate(eMatrix).toMATRIX() + ))); + Shape outline = dt.getOutline(dframe, time, ds.ratio, renderContext, outlineMatrix, true); + //ds.matrix = oldMatrix; + //bounds = outline.getBounds();*/ + } + repaint(); + } + mode = Cursor.DEFAULT_CURSOR; } } - }); - addMouseMotionListener(new MouseMotionAdapter() { + @Override public void mouseDragged(MouseEvent e) { - if (dragStart != null && allowMove) { + if (dragStart != null && allowMove && mode == Cursor.DEFAULT_CURSOR) { Point dragEnd = e.getPoint(); Point delta = new Point(dragEnd.x - dragStart.x, dragEnd.y - dragStart.y); offsetPoint.x += delta.x; @@ -294,8 +495,556 @@ public final class ImagePanel extends JPanel implements MediaDisplay { dragStart = dragEnd; repaint(); } + + if (dragStart != null && freeTransformDepth > -1) { + if (transform == null) { + return; + } + int ex = e.getX() - _rect.x; + int ey = e.getY() - _rect.y; + int dsx = dragStart.x - _rect.x; + int dsy = dragStart.y - _rect.y; + if (mode == MODE_SHEAR_N) { + + double shearX = -(ex - dsx) / (bounds.getHeight()); + + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(bounds.getX(), bounds.getY()); + t.shear(shearX, 0); + t.translate(-bounds.getX(), -bounds.getY()); + t.translate(ex - dsx, 0); + + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + if (mode == MODE_SHEAR_S) { + + double shearX = (ex - dsx) / (bounds.getHeight()); + + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(bounds.getX(), bounds.getY()); + t.shear(shearX, 0); + t.translate(-bounds.getX(), -bounds.getY()); + + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == MODE_SHEAR_W) { + double shearY = -(ey - dsy) / (bounds.getWidth()); + + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(bounds.getX(), bounds.getY()); + t.shear(0, shearY); + t.translate(-bounds.getX(), -bounds.getY()); + t.translate(0, ey - dsy); + + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + if (mode == MODE_SHEAR_E) { + double shearY = (ey - dsy) / (bounds.getWidth()); + + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(bounds.getX(), bounds.getY()); + t.shear(0, shearY); + t.translate(-bounds.getX(), -bounds.getY()); + + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == MODE_ROTATE_SE) { + double deltaStartX = Math.abs(dsx - registrationPoint.getX()); + double deltaStartY = Math.abs(dsy - registrationPoint.getY()); + + double deltaEndX = Math.abs(ex - registrationPoint.getX()); + double deltaEndY = Math.abs(ey - registrationPoint.getY()); + + double deltaTheta = 0; + if (ex >= registrationPoint.getX() && ey >= registrationPoint.getY()) { + //same + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = thetaEnd - thetaStart; + } else if (ex >= registrationPoint.getX() && ey <= registrationPoint.getY()) { + //anti clockwise + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = -(thetaStart + thetaEnd); + } else if (ex <= registrationPoint.getX() && ey >= registrationPoint.getY()) { + //clock wise + double thetaStart = Math.atan(deltaStartX / deltaStartY); + double thetaEnd = Math.atan(deltaEndX / deltaEndY); + deltaTheta = thetaStart + thetaEnd; + } else if (ex <= registrationPoint.getX() && ey <= registrationPoint.getY()) { + //opposite + double thetaStart = Math.atan(deltaStartX / deltaStartY); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = thetaStart + Math.toRadians(90) + thetaEnd; + } + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.rotate(deltaTheta, registrationPoint.getX(), registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == MODE_ROTATE_NW) { + double deltaStartX = Math.abs(dsx - registrationPoint.getX()); + double deltaStartY = Math.abs(dsy - registrationPoint.getY()); + + double deltaEndX = Math.abs(ex - registrationPoint.getX()); + double deltaEndY = Math.abs(ey - registrationPoint.getY()); + + double deltaTheta = 0; + if (ex >= registrationPoint.getX() && ey >= registrationPoint.getY()) { + //opposite + double thetaStart = Math.atan(deltaStartX / deltaStartY); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = thetaStart + Math.toRadians(90) + thetaEnd; + } else if (ex >= registrationPoint.getX() && ey <= registrationPoint.getY()) { + //clock wise + double thetaStart = Math.atan(deltaStartX / deltaStartY); + double thetaEnd = Math.atan(deltaEndX / deltaEndY); + deltaTheta = thetaStart + thetaEnd; + } else if (ex <= registrationPoint.getX() && ey >= registrationPoint.getY()) { + //anti clockwise + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = -(thetaStart + thetaEnd); + } else if (ex <= registrationPoint.getX() && ey <= registrationPoint.getY()) { + //same + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = thetaEnd - thetaStart; + } + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.rotate(deltaTheta, registrationPoint.getX(), registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == MODE_ROTATE_NE) { + double deltaStartX = Math.abs(dsx - registrationPoint.getX()); + double deltaStartY = Math.abs(dsy - registrationPoint.getY()); + + double deltaEndX = Math.abs(ex - registrationPoint.getX()); + double deltaEndY = Math.abs(ey - registrationPoint.getY()); + + double deltaTheta = 0; + if (ex >= registrationPoint.getX() && ey >= registrationPoint.getY()) { + //clock wise + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = thetaStart + thetaEnd; + } else if (ex >= registrationPoint.getX() && ey <= registrationPoint.getY()) { + //same + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = thetaStart - thetaEnd; + } else if (ex <= registrationPoint.getX() && ey >= registrationPoint.getY()) { + //opposite + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndX / deltaEndY); + deltaTheta = thetaStart + Math.toRadians(90) + thetaEnd; + } else if (ex <= registrationPoint.getX() && ey <= registrationPoint.getY()) { + //anti clockwise + double thetaStart = Math.atan(deltaStartX / deltaStartY); + double thetaEnd = Math.atan(deltaEndX / deltaEndY); + deltaTheta = -(thetaStart + thetaEnd); + } + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.rotate(deltaTheta, registrationPoint.getX(), registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == MODE_ROTATE_SW) { + double deltaStartX = Math.abs(dsx - registrationPoint.getX()); + double deltaStartY = Math.abs(dsy - registrationPoint.getY()); + + double deltaEndX = Math.abs(ex - registrationPoint.getX()); + double deltaEndY = Math.abs(ey - registrationPoint.getY()); + + double deltaTheta = 0; + if (ex >= registrationPoint.getX() && ey >= registrationPoint.getY()) { + //anti clockwise + double thetaStart = Math.atan(deltaStartX / deltaStartY); + double thetaEnd = Math.atan(deltaEndX / deltaEndY); + deltaTheta = -(thetaStart + thetaEnd); + } else if (ex >= registrationPoint.getX() && ey <= registrationPoint.getY()) { + //opposite + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndX / deltaEndY); + deltaTheta = thetaStart + Math.toRadians(90) + thetaEnd; + } else if (ex <= registrationPoint.getX() && ey >= registrationPoint.getY()) { + //same + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = thetaStart - thetaEnd; + } else if (ex <= registrationPoint.getX() && ey <= registrationPoint.getY()) { + //clock wise + double thetaStart = Math.atan(deltaStartY / deltaStartX); + double thetaEnd = Math.atan(deltaEndY / deltaEndX); + deltaTheta = thetaStart + thetaEnd; + } + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.rotate(deltaTheta, registrationPoint.getX(), registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == Cursor.HAND_CURSOR) { + transformUpdated = new AffineTransform(transform.toTransform()); + registrationPointUpdated = new Point2D.Double(ex, ey); + repaint(); + } + if (mode == Cursor.MOVE_CURSOR) { + int deltaX = ex - dsx; + int deltaY = ey - dsy; + + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(deltaX, deltaY); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == Cursor.E_RESIZE_CURSOR) { + double deltaBefore = bounds.getX() + bounds.getWidth() - registrationPoint.getX(); + double deltaX = ex - registrationPoint.getX(); + double scaleX = deltaX / deltaBefore; + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(registrationPoint.getX(), 0); + t.scale(scaleX, 1); + t.translate(-registrationPoint.getX(), 0); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + if (mode == Cursor.W_RESIZE_CURSOR) { + double deltaBefore = registrationPoint.getX() - bounds.getX(); + double deltaX = registrationPoint.getX() - ex; + double scaleX = deltaX / deltaBefore; + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(registrationPoint.getX(), 0); + t.scale(scaleX, 1); + t.translate(-registrationPoint.getX(), 0); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == Cursor.S_RESIZE_CURSOR) { + double deltaBefore = bounds.getY() + bounds.getHeight() - registrationPoint.getY(); + double deltaY = ey - registrationPoint.getY(); + double scaleY = deltaY / deltaBefore; + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(0, registrationPoint.getY()); + t.scale(1, scaleY); + t.translate(0, -registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + if (mode == Cursor.N_RESIZE_CURSOR) { + double deltaBefore = registrationPoint.getY() - bounds.getY(); + double deltaY = registrationPoint.getY() - ey; + double scaleY = deltaY / deltaBefore; + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(0, registrationPoint.getY()); + t.scale(1, scaleY); + t.translate(0, -registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + if (mode == Cursor.SE_RESIZE_CURSOR) { + double deltaXBefore = bounds.getX() + bounds.getWidth() - registrationPoint.getX(); + double deltaYBefore = bounds.getY() + bounds.getHeight() - registrationPoint.getY(); + double deltaX = ex - registrationPoint.getX(); + double deltaY = ey - registrationPoint.getY(); + double scaleX = deltaX / deltaXBefore; + double scaleY = deltaY / deltaYBefore; + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(registrationPoint.getX(), registrationPoint.getY()); + t.scale(scaleX, scaleY); + t.translate(-registrationPoint.getX(), -registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == Cursor.NE_RESIZE_CURSOR) { + double deltaXBefore = bounds.getX() + bounds.getWidth() - registrationPoint.getX(); + double deltaYBefore = registrationPoint.getY() - bounds.getY(); + double deltaX = ex - registrationPoint.getX(); + double deltaY = registrationPoint.getY() - ey; + double scaleX = deltaX / deltaXBefore; + double scaleY = deltaY / deltaYBefore; + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(registrationPoint.getX(), registrationPoint.getY()); + t.scale(scaleX, scaleY); + t.translate(-registrationPoint.getX(), -registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == Cursor.SW_RESIZE_CURSOR) { + double deltaXBefore = registrationPoint.getX() - bounds.getX(); + double deltaYBefore = bounds.getY() + bounds.getHeight() - registrationPoint.getY(); + double deltaX = registrationPoint.getX() - ex; + double deltaY = ey - registrationPoint.getY(); + double scaleX = deltaX / deltaXBefore; + double scaleY = deltaY / deltaYBefore; + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(registrationPoint.getX(), registrationPoint.getY()); + t.scale(scaleX, scaleY); + t.translate(-registrationPoint.getX(), -registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + + if (mode == Cursor.NW_RESIZE_CURSOR) { + double deltaXBefore = registrationPoint.getX() - bounds.getX(); + double deltaYBefore = registrationPoint.getY() - bounds.getY(); + double deltaX = registrationPoint.getX() - ex; + double deltaY = registrationPoint.getY() - ey; + double scaleX = deltaX / deltaXBefore; + double scaleY = deltaY / deltaYBefore; + AffineTransform newTransform = new AffineTransform(transform.toTransform()); + AffineTransform t = new AffineTransform(); + t.translate(registrationPoint.getX(), registrationPoint.getY()); + t.scale(scaleX, scaleY); + t.translate(-registrationPoint.getX(), -registrationPoint.getY()); + newTransform.preConcatenate(t); + + Point2D newRegistrationPoint = new Point2D.Double(); + t.transform(registrationPoint, newRegistrationPoint); + + transformUpdated = newTransform; + registrationPointUpdated = newRegistrationPoint; + repaint(); + } + } } - }); + + @Override + public void mouseMoved(MouseEvent e) { + if (freeTransformDepth > -1) { + if (bounds == null) { + return; + } + if (registrationPoint == null) { + return; + } + int ex = e.getX() - _rect.x; + int ey = e.getY() - _rect.y; + + boolean left = ex >= bounds.getX() - TOLERANCE_SCALESHEAR && ex <= bounds.getX() + TOLERANCE_SCALESHEAR; + boolean right = ex >= bounds.getX() + bounds.getWidth() - TOLERANCE_SCALESHEAR && ex <= bounds.getX() + bounds.getWidth() + TOLERANCE_SCALESHEAR; + boolean top = ey >= bounds.getY() - TOLERANCE_SCALESHEAR && ey <= bounds.getY() + TOLERANCE_SCALESHEAR; + boolean bottom = ey >= bounds.getY() + bounds.getHeight() - TOLERANCE_SCALESHEAR && ey <= bounds.getY() + bounds.getHeight() + TOLERANCE_SCALESHEAR; + + boolean xcenter = ex >= bounds.getCenterX() - TOLERANCE_SCALESHEAR && ex <= bounds.getCenterX() + TOLERANCE_SCALESHEAR; + boolean ycenter = ey >= bounds.getCenterY() - TOLERANCE_SCALESHEAR && ey <= bounds.getCenterY() + TOLERANCE_SCALESHEAR; + + boolean registration = ex >= registrationPoint.getX() - REGISTRATION_TOLERANCE + && ex <= registrationPoint.getX() + REGISTRATION_TOLERANCE + && ey >= registrationPoint.getY() - REGISTRATION_TOLERANCE + && ey <= registrationPoint.getY() + REGISTRATION_TOLERANCE; + + boolean rightRotate = ex > bounds.getX() + bounds.getWidth() - TOLERANCE_ROTATE && ex + <= bounds.getX() + bounds.getWidth() + TOLERANCE_ROTATE; + boolean bottomRotate = ey > bounds.getY() + bounds.getHeight() - TOLERANCE_ROTATE && ey + <= bounds.getY() + bounds.getHeight() + TOLERANCE_ROTATE; + + boolean leftRotate = ex < bounds.getX() + TOLERANCE_ROTATE + && ex >= bounds.getX() - TOLERANCE_ROTATE; + + boolean topRotate = ey < bounds.getY() + TOLERANCE_ROTATE + && ey >= bounds.getY() - TOLERANCE_ROTATE; + + boolean inBounds = bounds.contains(ex, ey); + + boolean shearX = ex > bounds.getX() && ex < bounds.getX() + bounds.getWidth(); + boolean shearY = ey > bounds.getY() && ey < bounds.getY() + bounds.getHeight(); + + Cursor cursor; + if (top && left) { + mode = Cursor.NW_RESIZE_CURSOR; + cursor = resizeNWSECursor; + } else if (bottom && left) { + mode = Cursor.SW_RESIZE_CURSOR; + cursor = resizeSWNECursor; + } else if (top && right) { + mode = Cursor.NE_RESIZE_CURSOR; + cursor = resizeSWNECursor; + } else if (bottom && right) { + mode = Cursor.SE_RESIZE_CURSOR; + cursor = resizeNWSECursor; + } else if (top && xcenter) { + mode = Cursor.N_RESIZE_CURSOR; + cursor = resizeYCursor; + } else if (bottom && xcenter) { + mode = Cursor.S_RESIZE_CURSOR; + cursor = resizeYCursor; + } else if (left && ycenter) { + mode = Cursor.W_RESIZE_CURSOR; + cursor = resizeXCursor; + } else if (right && ycenter) { + mode = Cursor.E_RESIZE_CURSOR; + cursor = resizeXCursor; + } else if (registration) { + mode = Cursor.HAND_CURSOR; + cursor = moveRegPointCursor; + } else if (!inBounds && rightRotate && topRotate) { + mode = MODE_ROTATE_NE; + cursor = rotateCursor; + } else if (!inBounds && rightRotate && bottomRotate) { + mode = MODE_ROTATE_SE; + cursor = rotateCursor; + } else if (!inBounds && leftRotate && topRotate) { + mode = MODE_ROTATE_NW; + cursor = rotateCursor; + } else if (!inBounds && leftRotate && bottomRotate) { + mode = MODE_ROTATE_SW; + cursor = rotateCursor; + } else if (shearY && (left || right)) { + if (left) { + mode = MODE_SHEAR_W; + } else { + mode = MODE_SHEAR_E; + } + cursor = shearYCursor; + } else if (shearX && (top || bottom)) { + if (top) { + mode = MODE_SHEAR_N; + } else { + mode = MODE_SHEAR_S; + } + cursor = shearXCursor; + } else if (inBounds) { + mode = Cursor.MOVE_CURSOR; + cursor = moveCursor; + } else { + mode = Cursor.DEFAULT_CURSOR; + cursor = selectCursor; + } + + setCursor(cursor); + } + } + + }; + addMouseListener(mouseInputAdapter); + addMouseMotionListener(mouseInputAdapter); } public void setAutoFit(boolean autoFit) { @@ -506,7 +1255,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { lastMouseEvent = e; redraw(); ButtonTag button = iconPanel.mouseOverButton; - if (button != null) { + if (button != null && freeTransformDepth == -1) { DefineButtonSoundTag sounds = button.getSounds(); if (sounds != null && sounds.buttonSoundChar2 != 0) { // OverUpToOverDown playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar2), sounds.buttonSoundInfo2, timer); @@ -522,7 +1271,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { lastMouseEvent = e; redraw(); ButtonTag button = iconPanel.mouseOverButton; - if (button != null) { + if (button != null && freeTransformDepth == -1) { DefineButtonSoundTag sounds = button.getSounds(); if (sounds != null && sounds.buttonSoundChar3 != 0) { // OverDownToOverUp playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar3), sounds.buttonSoundInfo3, timer); @@ -815,8 +1564,9 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private void clear() { if (timer != null) { - timer.cancel(); + Timer ptimer = timer; timer = null; + ptimer.cancel(); fireMediaDisplayStateChanged(); } @@ -861,7 +1611,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { fireMediaDisplayStateChanged(); } - private static SerializableImage getFrame(SWF swf, int frame, int time, Timelined drawable, RenderContext renderContext, int selectedDepth, double zoom) { + private static SerializableImage getFrame(SWF swf, int frame, int time, Timelined drawable, RenderContext renderContext, int selectedDepth, int freeTransformDepth, double zoom, Reference registrationPointRef, Reference boundsRef, Matrix transform, Matrix temporaryMatrix) { Timeline timeline = drawable.getTimeline(); //int mouseButton = renderContext.mouseButton; //Point cursorPosition = renderContext.cursorPosition; @@ -878,17 +1628,30 @@ public final class ImagePanel extends JPanel implements MediaDisplay { (int) Math.ceil(height / SWF.unitDivisor), SerializableImage.TYPE_INT_ARGB); //renderContext.borderImage = new SerializableImage(image.getWidth(), image.getHeight(), SerializableImage.TYPE_INT_ARGB); image.fillTransparent(); + Matrix m = new Matrix(); m.translate(-rect.Xmin * zoom, -rect.Ymin * zoom); m.scale(zoom); + MATRIX oldMatrix = null; + if (freeTransformDepth > -1) { + + Matrix eMatrix = Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m).inverse(); + + MATRIX newMatrix = transform.preConcatenate(eMatrix).toMATRIX(); + oldMatrix = timeline.getFrame(frame).layers.get(freeTransformDepth).matrix; + + timeline.getFrame(frame).layers.get(freeTransformDepth).matrix = newMatrix; + } + timeline.toImage(frame, time, renderContext, image, false, m, m, m, null); + Graphics2D gg = (Graphics2D) image.getGraphics(); gg.setStroke(new BasicStroke(3)); gg.setPaint(Color.green); gg.setTransform(AffineTransform.getTranslateInstance(0, 0)); DepthState ds = null; - if (timeline.getFrameCount() > frame) { + if (selectedDepth > -1 && timeline.getFrameCount() > frame) { ds = timeline.getFrame(frame).layers.get(selectedDepth); } @@ -915,6 +1678,50 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } } + ds = null; + if (freeTransformDepth > -1 && timeline.getFrameCount() > frame) { + ds = timeline.getFrame(frame).layers.get(freeTransformDepth); + } + + if (ds != null) { + CharacterTag cht = swf.getCharacter(ds.characterId); + if (cht != null) { + if (cht instanceof DrawableTag) { + DrawableTag dt = (DrawableTag) cht; + int drawableFrameCount = dt.getNumFrames(); + if (drawableFrameCount == 0) { + drawableFrameCount = 1; + } + + int dframe = time % drawableFrameCount; + //Matrix finalMatrix = Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m).concatenate(new Matrix(ds.matrix)); + Shape outline = dt.getOutline(dframe, time, ds.ratio, renderContext, transform, true); + + if (temporaryMatrix != null) { + Shape tempOutline = dt.getOutline(dframe, time, ds.ratio, renderContext, temporaryMatrix, true); + gg.setStroke(new BasicStroke(1)); + gg.setPaint(Color.black); + gg.draw(tempOutline); + } + + Rectangle bounds = outline.getBounds(); + boundsRef.setVal(bounds); + gg.setStroke(new BasicStroke(1)); + gg.setPaint(Color.black); + gg.draw(bounds); + drawHandles(gg, bounds); + + if (registrationPointRef.getVal() == null) { + registrationPointRef.setVal(new Point2D.Double(bounds.getCenterX(), bounds.getCenterY())); + } + drawRegistrationPoint(gg, registrationPointRef.getVal()); + } + } + } + + if (freeTransformDepth > -1) { + timeline.getFrame(frame).layers.get(freeTransformDepth).matrix = oldMatrix; + } img = image; /*if (shouldCache) { @@ -924,6 +1731,45 @@ public final class ImagePanel extends JPanel implements MediaDisplay { return img; } + private static void drawRegistrationPoint(Graphics2D g2, Point2D registrationPoint) { + Stroke stroke = new BasicStroke(1); + g2.setStroke(stroke); + g2.setColor(Color.white); + Shape registrationPointShape = new Ellipse2D.Double(registrationPoint.getX() - CENTER_POINT_SIZE / 2, + registrationPoint.getY() - CENTER_POINT_SIZE / 2, + CENTER_POINT_SIZE, + CENTER_POINT_SIZE); + g2.fill(registrationPointShape); + g2.setColor(Color.black); + g2.draw(registrationPointShape); + } + + private static void drawHandles(Graphics2D g2, Rectangle bounds) { + drawHandle(g2, bounds.getX(), bounds.getY()); + drawHandle(g2, bounds.getCenterX(), bounds.getY()); + drawHandle(g2, bounds.getX() + bounds.getWidth(), bounds.getY()); + drawHandle(g2, bounds.getX(), bounds.getCenterY()); + drawHandle(g2, bounds.getX(), bounds.getY() + bounds.getHeight()); + drawHandle(g2, bounds.getX() + bounds.getWidth(), bounds.getCenterY()); + drawHandle(g2, bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()); + drawHandle(g2, bounds.getCenterX(), bounds.getY() + bounds.getHeight()); + } + + private static void drawHandle(Graphics2D g2, double x, double y) { + Shape handleTopCenter = new Rectangle2D.Double( + x - HANDLES_WIDTH / 2, + y - HANDLES_WIDTH / 2, + HANDLES_WIDTH + HANDLES_STROKE_WIDTH, + HANDLES_WIDTH + HANDLES_STROKE_WIDTH); + g2.setColor(Color.black); + g2.fill(handleTopCenter); + + Stroke stroke = new BasicStroke(HANDLES_STROKE_WIDTH); + g2.setStroke(stroke); + g2.setColor(Color.white); + g2.draw(handleTopCenter); + } + private Object execute(SWFInputStream sis) throws IOException { if (!Configuration.internalFlashViewerExecuteAs12.get()) { return Undefined.INSTANCE; @@ -1021,7 +1867,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { RenderContext renderContext = new RenderContext(); renderContext.displayObjectCache = displayObjectCache; - if (cursorPosition != null) { + if (cursorPosition != null && freeTransformDepth == -1) { renderContext.cursorPosition = new Point((int) (cursorPosition.x * SWF.unitDivisor), (int) (cursorPosition.y * SWF.unitDivisor)); } @@ -1048,7 +1894,15 @@ public final class ImagePanel extends JPanel implements MediaDisplay { img = null; if (display) { Stopwatch sw = Stopwatch.startNew(); - img = getFrame(swf, frame, time, timelined, renderContext, selectedDepth, zoomDouble); + + synchronized (lock) { + Reference registrationPointRef = new Reference<>(registrationPoint); + Reference boundsRef = new Reference<>(bounds); + img = getFrame(swf, frame, time, timelined, renderContext, selectedDepth, freeTransformDepth, zoomDouble, registrationPointRef, boundsRef, transform, transformUpdated == null ? null : new Matrix(transformUpdated)); + bounds = boundsRef.getVal(); + registrationPoint = registrationPointRef.getVal(); + } + sw.stop(); if (sw.getElapsedMilliseconds() > 100) { logger.log(Level.WARNING, "Slow rendering. {0}. frame, time={1}, {2}ms", new Object[]{frame, time, sw.getElapsedMilliseconds()}); @@ -1115,17 +1969,19 @@ public final class ImagePanel extends JPanel implements MediaDisplay { lastMouseOverButton = iconPanel.mouseOverButton; iconPanel.mouseOverButton = renderContext.mouseOverButton; debugLabel.setText(ret.toString()); - if (handCursor) { - iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } else if (iconPanel.hasAllowMove()) { - iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); - } else { - iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if (freeTransformDepth == -1) { + if (handCursor) { + iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } else if (iconPanel.hasAllowMove()) { + iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); + } else { + iconPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } } if (lastMouseOverButton != renderContext.mouseOverButton) { ButtonTag b = renderContext.mouseOverButton; - if (b != null) { + if (b != null && freeTransformDepth == -1) { // New mouse entered DefineButtonSoundTag sounds = b.getSounds(); if (sounds != null && sounds.buttonSoundChar1 != 0) { // IdleToOverUp @@ -1134,7 +1990,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } b = lastMouseOverButton; - if (b != null) { + if (b != null && freeTransformDepth == -1) { // Old mouse leave DefineButtonSoundTag sounds = b.getSounds(); if (sounds != null && sounds.buttonSoundChar0 != 0) { // OverUpToIdle @@ -1298,6 +2154,9 @@ public final class ImagePanel extends JPanel implements MediaDisplay { long frameTimeMsIs = System.currentTimeMillis(); //Total number of frames in this timeline int frameCount = timelined.getTimeline().getFrameCount(); + if (frameCount == 0) { + return; + } //How many ticks (= times where frame should be displayed in framerate) are there from hitting play button int ticksFromStart = (int) Math.floor((frameTimeMsIs - startRun) / (double) getMsPerFrame()) + 1; //Add ticks to first frame when hitting play button, ignoring total framecount => this value can be larger than number of frames in timeline diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index b921317bd..c7d3aa702 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -130,6 +130,7 @@ import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.ImageTag; import com.jpexs.decompiler.flash.tags.base.MissingCharacterHandler; import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; @@ -3474,6 +3475,10 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se previewPanel.showSwf(swf); } } + } else if (treeItem instanceof PlaceObjectTypeTag) { + TreePath path = tagTree.getModel().getTreePath(treeItem); + Frame frame = (Frame) path.getParentPath().getLastPathComponent(); + previewPanel.showPlaceTagPanel((PlaceObjectTypeTag) treeItem, frame.frame); } else if (treeItem instanceof MetadataTag) { MetadataTag metadataTag = (MetadataTag) treeItem; previewPanel.showMetaDataPanel(metadataTag); @@ -3655,6 +3660,9 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } else if ((treeItem instanceof Frame) || (treeItem instanceof CharacterTag) || (treeItem instanceof FontTag) || (treeItem instanceof SoundStreamHeadTypeTag)) { showPreview(treeItem, previewPanel); + showCard(CARDPREVIEWPANEL); + } else if (treeItem instanceof PlaceObjectTypeTag) { + showPreview(treeItem, previewPanel); showCard(CARDPREVIEWPANEL); } else if (treeItem instanceof Tag) { showGenericTag((Tag) treeItem); diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java index eeb51f0d2..3860c1bce 100644 --- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java @@ -32,11 +32,13 @@ import com.jpexs.decompiler.flash.tags.MetadataTag; import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.timeline.Frame; import com.jpexs.decompiler.flash.timeline.TagScript; import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.treeitems.TreeItem; +import com.jpexs.decompiler.flash.types.MATRIX; import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; import com.jpexs.helpers.SerializableImage; import java.awt.BorderLayout; @@ -93,6 +95,8 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private static final String CARDFONTPANEL = "Font card"; + private static final String PLACE_TAG_CARD = "PLACETAG"; + private final MainPanel mainPanel; private final JPanel viewerCards; @@ -141,6 +145,12 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private JButton genericCancelButton; + private JButton placeEditButton; + + private JButton placeSaveButton; + + private JButton placeCancelButton; + private JPanel parametersPanel; private FontPanel fontPanel; @@ -153,8 +163,14 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private boolean readOnly = false; + private ImagePanel placeImagePanel; + private final int dividerSize; + private PlaceObjectTypeTag placeTag; + + private MATRIX oldMatrix; + public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; setDividerSize(this.readOnly ? 0 : dividerSize); @@ -176,6 +192,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel viewerCards.add(createBinaryCard(), BINARY_TAG_CARD); viewerCards.add(createMetadataCard(), METADATA_TAG_CARD); viewerCards.add(createGenericTagCard(), GENERIC_TAG_CARD); + viewerCards.add(createPlaceTagCard(), PLACE_TAG_CARD); viewerCards.add(createEmptyCard(), EMPTY_CARD); setLeftComponent(viewerCards); @@ -403,6 +420,49 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel return genericTagCard; } + private JPanel createPlaceTagCard() { + JPanel placeTagCard = new JPanel(new BorderLayout()); + + JPanel previewPanel = new JPanel(new BorderLayout()); + + JPanel previewCnt = new JPanel(new BorderLayout()); + placeImagePanel = new ImagePanel(); + //imagePanel.setLoop(Configuration.loopMedia.get()); + previewCnt.add(placeImagePanel, BorderLayout.CENTER); + PlayerControls placeImagePlayControls = new PlayerControls(mainPanel, placeImagePanel); + previewCnt.add(placeImagePlayControls, BorderLayout.SOUTH); + placeImagePlayControls.setMedia(placeImagePanel); + previewPanel.add(previewCnt, BorderLayout.CENTER); + JLabel prevIntLabel = new HeaderLabel(mainPanel.translate("swfpreview.internal")); + prevIntLabel.setHorizontalAlignment(SwingConstants.CENTER); + previewPanel.add(prevIntLabel, BorderLayout.NORTH); + + placeTagCard.add(previewPanel, BorderLayout.CENTER); + placeTagCard.add(createPlaceTagButtonsPanel(), BorderLayout.SOUTH); + + return placeTagCard; + } + + private JPanel createPlaceTagButtonsPanel() { + placeEditButton = new JButton(mainPanel.translate("button.edit"), View.getIcon("edit16")); + placeEditButton.setMargin(new Insets(3, 3, 3, 10)); + placeEditButton.addActionListener(this::editPlaceTagButtonActionPerformed); + placeSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16")); + placeSaveButton.setMargin(new Insets(3, 3, 3, 10)); + placeSaveButton.addActionListener(this::savePlaceTagButtonActionPerformed); + placeSaveButton.setVisible(false); + placeCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16")); + placeCancelButton.setMargin(new Insets(3, 3, 3, 10)); + placeCancelButton.addActionListener(this::cancelPlaceTagButtonActionPerformed); + placeCancelButton.setVisible(false); + + ButtonsPanel placeTagButtonsPanel = new ButtonsPanel(); + placeTagButtonsPanel.add(placeEditButton); + placeTagButtonsPanel.add(placeSaveButton); + placeTagButtonsPanel.add(placeCancelButton); + return placeTagButtonsPanel; + } + private void showCardLeft(String card) { CardLayout cl = (CardLayout) (viewerCards.getLayout()); cl.show(viewerCards, card); @@ -567,6 +627,19 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel parametersPanel.setVisible(false); } + public void showPlaceTagPanel(PlaceObjectTypeTag tag, int frame) { + showCardLeft(PLACE_TAG_CARD); + placeTag = tag; + oldMatrix = tag.getMatrix(); + placeImagePanel.setTimelined(((Tag) tag).getTimelined(), ((Tag) tag).getSwf(), frame); + placeImagePanel.selectDepth(tag.getDepth()); + parametersPanel.setVisible(false); + placeEditButton.setVisible(!tag.isReadOnly()); + placeEditButton.setEnabled(true); + placeSaveButton.setVisible(false); + placeCancelButton.setVisible(false); + } + public void setImageReplaceButtonVisible(boolean show, boolean showAlpha) { if (readOnly) { show = false; @@ -728,6 +801,41 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel genericTagPanel.setEditMode(false, null); } + private void savePlaceTagButtonActionPerformed(ActionEvent evt) { + MATRIX matrix = placeImagePanel.getNewMatrix(); + placeTag.setMatrix(matrix); + placeTag.setModified(true); + placeImagePanel.selectDepth(placeTag.getDepth()); + placeImagePanel.freeTransformDepth(-1); + placeTag.getTimelined().resetTimeline(); + placeEditButton.setVisible(true); + placeSaveButton.setVisible(false); + placeCancelButton.setVisible(false); + } + + private void editPlaceTagButtonActionPerformed(ActionEvent evt) { + TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); + if (item == null) { + return; + } + placeImagePanel.selectDepth(-1); + placeImagePanel.freeTransformDepth(placeTag.getDepth()); + placeEditButton.setVisible(false); + placeSaveButton.setVisible(true); + placeCancelButton.setVisible(true); + } + + private void cancelPlaceTagButtonActionPerformed(ActionEvent evt) { + placeImagePanel.selectDepth(placeTag.getDepth()); + placeImagePanel.freeTransformDepth(-1); + placeTag.setMatrix(oldMatrix); + placeTag.getTimelined().resetTimeline(); + + placeEditButton.setVisible(true); + placeSaveButton.setVisible(false); + placeCancelButton.setVisible(false); + } + private void prevFontsButtonActionPerformed(ActionEvent evt) { FontTag fontTag = fontPanel.getFontTag(); int pageCount = getFontPageCount(fontTag); diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/cursors/move.png b/src/com/jpexs/decompiler/flash/gui/graphics/cursors/move.png new file mode 100644 index 0000000000000000000000000000000000000000..39578cbb784df46cf050fe058c6724fc3a842e45 GIT binary patch literal 731 zcmV<10wn#3P)EX>4Tx04R}tkv&MmKpe$iQ>7vmK|6>zWT=8*K~%)CRIvyaN?V~-2a`*`ph-iL z;^HW{799LotU9+0Yt2!bCVZf;JBE>hzEl0u6Z503ls?%w0>9UwF+Of|bE0ade% zR6HhTbE{(D6@CZ^U>pMyGxbDzF$2%>bq^n3?;bkx9)Hhls^u2g@DIN`^{2LmXCAjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}md4AdV#@kc0>sHIz|-g$S)0DJD|1AM@}JJN_iOWO9|k z$gzMbR7j2={11M2YZj)b+@xR(=zg*7k5M473pDGt{e5iP%@e@?3|wh#f3*S3eUe^p zYvCiHe;c^CZfo)$aJd5vJ?WAmIg+2IP$&TJXY@@uVDJ{`U2}VD?c?+T$WT|yH^9Lm zFjl1Ob&q%VboTb|nO1*4J@j&tVuNbc00006VoOIv0RI30001*5eDeSR010qNS#tmY z5_A9n5_AFHW*>L}000McNliru+0N6=HK~z}7?Uu_9#UKbohl&6H z%iK*#h_ODnDw~otsX{wP5L>9KBGCu{nGgg35lMrf-DE+~4>BNFPuzxJYw{)p-yF9f z*mK^1;3(iU1ZRRyLU1_j1fk>CpHzm^u@SDTqROd^>lWlG|RNJIyzH7Z=5ia|t%8iTNO zIZh&_U+OK3U+v;%n|Jk%pt5XtsuW()e0%ceBfrwiE*9c{N}gs%gcJi2&>$1 zsIlw5O~CSd`5IwKhjOLLwzo*;^`6%yrDtzm?lG%Nb~hC99QUnd@t#>J?bVK#m8^rU z-$gEw$7+Xq$RD%`Ml7|{9SYvt$TefCn58Wx(JZG@-ta4mhT-bhCf<0fyl11AcFB)D zEpHFHcsd+i_4bB433-RVTp0H2x`A`ngBue+s=Re@Fs!oRAk@63umUgqs+W2^yErc^ z>K`#N_BJ;D)H_GA9agOwzV2leWcT-D6EY58aM}@??;K|VC-0C)tW+GZ&M#UR{8`hq zy*GD;-l&hRdZRodNxj2eJoPiL0qe#0Tm8?fmiKwho5lSm)Wdn-E^9pIQ%`%lt&FJm zPj}Cl-_^M*x0um*ZstK!ZbM1%x>tVjJl1x@3+js9InAZ%XsXASO5rhlPU8ehTvvdaF|T^4Qrp*_IX%JSl(&UwXKn@TKKNd_9}`xg}1OewzCvA1-02x-=jw_W-Jm#2I` z#3^LiKwnJEU$ou*?F)j7#l9``G@fe)zg^?HcDQ?cvtN#lPwtdD!H8x@*9Y6&8a`y4 zs`?=8ON*@^FW!E$UZmzdm>RkCy~r~&!dMFuzt~+3-TEeI%lxl$9SYu+Xo4O{VzcbL zW4ygDbkvCI;1>S^aeQ)ZRIpcCTb8|Wb?-;FHb`8U9RqUUm;|$0zRW4#)itMhO>^($Bb@b$kEb2BJlCwYxM1z$yWmXKFPpCJ zzZE}pVY&~4-Kx2b_Plu{XH(Nq8+{w|!L5d-`K=w!nU&o;Gea*QM2A26IQ+t+0%zf& z?oaqbwwF$XoUV-C-Q5)H*l+J}cWyNI`uT(hHmz&j-1wl?X%X7R@@fGU@<%n%~0BJlTgi34W6LYDu-FQ)v8O zqbF@&a$>J2ptQ5ov&L!GNL&@ahIgm?2QSMUS4gQ0+d_@(d|c{Z;A~&?aIo*n^}g8< z+iR)!aSyZT!-W!o2d+IR{bAZVOUr~=<_@f%0y7!r&4X$7t}FY=g0l}h_UsF9{EO{C ze*Llv7pFms!Jdi(+AFV5t}fj_WshUHGvU`6{K;ABiB(kof`O*u645!wO`A_iC!L+$ z6q*+*2$&Tk*?MQ5XN!wCaU zU2kgFr1V}NHai^=dzO3hYd3n#{+_gLx_iXWKlks7S??HsWB9+IiS zqB6rIXl4ou%gDYy)`E0CAW&cg0;MZbm0EtfkZj=Pga7(v8W}P`h!i0?P8I9dHIwXP4GAdEzVH%xDV=^fK zLD6O?2_&7O)XvpY7&!tlEvk{L3AsuM={XUpDvc15$zUHEB?r4`P&!x~RiM8&&}#`9 zO$2V(z#jmhG3j(3h0dffVVbEt*cFS%q?OvyD1vy>(h)U{L8a3ail-#BM9}JSeb1E8 zO2DIt7Kv$9X&Mv@T8$}*xu#0hscBkMooQN3zhtmGRff}mQ3lPXGQlEo#F&gejEQoE z+907vn<8cC7*3t0Ni|?(C=E-+6u=NIU}ijpC*=5ef}Zk6zw;YIfVwgMr_fL0Ww6B% zSAL)hP17qC1q#V}d-*aIDwpvMe{nh;V25G9J~sQ@`u4tUs1sSH68CWXgD=oGe$!=NB27pI^w!a#AU3`01G2}QU@4mtrz zHML4lB?D9p28)ZsFizp291exef-zu?j7woNFc~6avk{aI8>sYo;QK|0gk&a_KE4u> ziV(O;qX6wCSISg6?f8~NuE3%QL?1Q=hr#B+3?7fg@n+CjtZ`5brqO~j*RwL{R3_7~ zBSZO%0Ve{=O|C!^F`8PLXxPvvh7a5Up+)rF0hkTzz#D#m215vyMxs)s3dwqFAw8v` zsUX2(I17aGTs?SQmWh#DhM)+0X~8n z!>>hFV=_YuKpc%*XcD4K#K62AZMG-t@@Et>odt7H97ZTS1ZPs%T!uFV!NE}ENa->r zo6eFVTw{XA*tII0&>xL$TMgzL!Kee(fXVBv^BaUOkZ4dCWAs}fjKB)u=p&P z&*G4c*&faQpW_}XMR{Bvj!|&%OJU<2(CQpuiHyf(N@1oq%0Z2-{)f8f&Ec~d{}1<= zQU(v>(is#OkzrtxNqH2Q1+s_3TskVn+1?n}*y(?udu%?7!Dq6bxF?|LpN~&!RiJ-@ zh{b%H7bIGm#&iFux~m3NuOvsMHx?1}72WI^}cK zKLcoFSSm*`rB?Mk(@i19u$UYpfX&7=a6AF09opD&XSBybhW^aY=))!dUSB%|E>`N0H?$;@1EK$#UBO+`!Qv!BBo3PNqxl3o*XeEtwwwe@WuiboNnBm; zcE8DWknKcW?ES}O$EFwm(`!oW)Z>$y=T*-ZSWIYNOrDUv)XI$1$hP`v(*mpAE1B-Y zGd90_KAIlszb|k{F5YNqb$MFPyXQk&!mJKC^_BAQ#?7wRYLY|m3g9{_lJc$3{mVy> z4?1qRvi7Hdc8~ib%EF2y#=TO%KEp9tR~t!;f4ajUH@zR)UL6cDl2T4nD9gwVQn{l4vgCSQ`7dw=J5 z&i&nU&duZ}#K(Ghj`8GhI9}Sg=tS@fu^*42;G53L>E>|U!*UEWnMA}6*(lOPm~n{7 zw&4)&AWR&N2)<&%LIL(39L*n!vqx^T$4O_vf@$TR>8iq3M~ZJHmiE{RyP(+7Df#Is4CkcKY_- zjp0@I7FHjz@NboR%e)&WOvuq)j9pt>01HjxR^d6}0E6J4EYhM8E#C z)`u54rck$W&MZx6^GIse?wHbLbDH!YVb9Tr&Us!wo?KsUICAiSI4Gea^37l8zdSvT z+pH|dcX4E2P4!9N)pE9FS>&4~7-3v8YiY9Z$a{XXb8k|~hcE1Vc80iN)NzBY`q%uf zlP5+=e$20%Ta_o+r1cnG(!8U}b8^Op>@6=JNYAbdOm>Tyi^$D;ez7R3g2&a!ZpQg# zG(^VKF3v3d9^p*LBbSfz8QHlyI=EPWEZ<{+v@=+Ug)UV<&~$6U;fR2z3T|}2WB1wl z>5e+@OUIt_SQ9inF2~(n8;j$0o0GU+P#lr09hD{p^a7-|3i=_ZwI3jvO75^%)_} zUz*l>P}OR@9n)U0e#Aw-^^IcM_pH7Me2t*a*J#R5U)im@)#6uvXY|))ISp4=#w_2- zU0+X}9Jc8SnUH-0t8RNcJiKEs?4^r%A!Yp8+U=HZ^228RwmUn^(?3kz&gijoEAAA^ zYV7hC?@g(=8`za{v%b5||Jsxluj{``Zp$5d|Em)Z-sgn@?u*x5Se$&@-(&x0%A_rw zw>ccQWkgg|f;KAZK@EY**|2D}ChqgeW51Y}w3YXKMEF}_*tWd12*bfDLzRc$Yjet{ z4=b5cU#5*JZfObH=QsAZnLAYb!rFei5aN;N4_U48e-|XS^b|+FJ<7Y_?v3_ym)gfc z(vYdL+CaDI)vFBZKok9u@xt@D9v*39-F+mtqH{#K6!9p`FKyR%?&@u6*kYt`*`n|aA})u* zlXBt>Xig@o#CVfl@l-ohfWU$?2;{Jst+dLa;W>F#;F=wVd5{yrWNLUbbqP=uN#T%G zAQcGtF%Dv(nD>e&q^2;FDlz)iJ_>NB;bkz4O$EbtyIo+H2uLa&7AciVSSW_YVm?6d z>1->5IQUk2B1_TB5slL*Mc5dEv_dQ=VkEN|4UY%rp+0gji^e#>V4nl_-bqh07_0?1 zQm`HXfW<;#7+)yniWpNc?F*n!w!kw6GrEQ2g)CT8J)zlU1T25_{% zi8xJWQ79g>5VtZDU7gy@S+uLqEE;EroN=2m6AXfKdUmym)#?)ZZCDx835(5X!J=J} z7}}4sWl?4)218-oj9Wk;G+-7D!ZUyH=z$qG}Im;>%3dFr(W2ov?z=?o#BP>Wd z4%@8hP7`)xRA4zkT7<0*!0a3ct5HQ!IKq&Wfh5fu9vdyhQaXzYQumdMYAT634V_>d zV=Jz&<|2`F*m0O;5|g=Qeu zbR6{CzG8bYPCO)$#h3z-8)b5kZc3alMG=sFs8GSjP?^{$F(FEkQ8t*JCQXbTq40=w zkON?!L2)_v84Bvlzu>`mdj`%XmryL?3nicr8bnf+M5Gdh@p^X~Qp0SAeXv?J+k5gCVmvI>IpX^XuOA49RWn z8QxGXo!otI^gllKnit*}`N^1ybDO$6PgkGM8uB^k)W(&6Yf65v=q>O%1UCzenjXEX>4Tx04R}tkv&MmKpe$iQ>7vmK|6>zWT=8*K~%)CRIvyaN?V~-2a`*`ph-iL z;^HW{799LotU9+0Yt2!bCVZf;JBE>hzEl0u6Z503ls?%w0>9UwF+Of|bE0ade% zR6HhTbE{(D6@CZ^U>pMyGxbDzF$2%>bq^n3?;bkx9)Hhls^u2g@DIN`^{2LmXCAjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}md4AdV#@kc0>sHIz|-g$S)0DJD|1AM@}JJN_iOWO9|k z$gzMbR7j2={11M2YZj)b+@xR(=zg*7k5M473pDGt{e5iP%@e@?3|wh#f3*S3eUe^p zYvCiHe;c^CZfo)$aJd5vJ?WAmIg+2IP$&TJXY@@uVDJ{`U2}VD?c?+T$WT|yH^9Lm zFjl1Ob&q%VboTb|nO1*4J@j&tVuNbc00006VoOIv0RI30001*5eDeSR010qNS#tmY z5_A9n5_AFHW*>L}000McNliruOU zf5*5hUUer@9US3R>nBS%hqrIRwG$l!WJl4=%*@Qper*FSaWoYrC>)gl0000WlZ23j1Pfsc8pI^BxU@`WCNL#iX9g1yQLGjz zh>8WZsOWR4ZPl-QRK%sl4VMS{)Y8^gTpprUk%EHTQ}o>li1F2DpWpZTpUD@Jd++c3 z&bhyP&bdrsQevE!ho1+F#qv_cM{B@0#C+V{z+WmSr<=uc<>cs6X$@k7tfa+27;%W6 zX~iMjP8e7$`>*ZgQ_Iglo}Zjgi&;K5=smr3MT7KgRQddf zmBnp!dER$iU!Uc{jo$v5_GWFA_sYS+m+ySNWle5s&#t?v`@2>*Pan*08@T@Z;&SgE zZQ}C$*&!?HM=FySFKEvjU2x!g#R!@I(c;Y5litdE{GdI&6Uf@@XKs$EIA=!M?w>xs z`gGpzUoO??6qf=&FSxSy$ooZ|fsNDSS1NBMLzeIV<+W84>{cF`?>F49>F3lh*$4No z9v(;y+&o4&>G-tz-Sw|7a{C%1^mEhlQ=m)l_)f{|B2$iDs2wU=nORpv49^SZtcr!01=HpoPS08@9 zeMRawnMa!%(`rhkgS`*0(EenUUeR3mb}zZ_%s`Kg#yv-UYic*D!_M4H9{cw%KJm-? zYRa)6r;WcD>~$r2yt-n0bHW!TnrrJ0uh})DZRhfQ%}Bxig@In9#5Y4;4<*BY@@a2t z`fbCZq9$Eak>C5173vf6#1_m=@9%yvd)RxCUD)MCQR!hP)rJV+_n#hFC`gnn3WkSx zu9kT3I^8qccKFzW&Xnf6L05uz&51m&rg=wZx89yVrJ_#!W6{+4mop^Gt_`dV_{?ME z!_EqNQCG_5VpDhc)ni}X$L>vCKWi($u|szX`Q6=HnP-3AUzsRAJ?_BW z%`FktH&$QY*|FhP#I{-GzfUf$YdF<2s(Z(}$L~icmUuoGS9;dcy{xWfoCmLg#Tqc5 zP%4vDO6B9i0p+r4_EJUsf%p9DKhSPu504CAD8V+*OOMnYtkPAv`zB0XH+j&q@pWre z%F>pWkXm2=2dP_RwUV}L7sk5J3xLe##80`JmPe&gMI(Gl?%(+R+{NEVL87q}LU#ub zm|R_~lLs59uk{y(=efJ5`@0Sk-ipqFUH@?-+b7_I4z|4EeskseiAO6uI|~m^+BDMl zhTDznn>JGC-uA94-7uu`wTUC>$dGoDPZH!x z|76{wn+vuycwFHe|8V1>DR)|Vo|m_miGC(Zppv(Omdtm5BlNRL`3u~)`i%+Jd>hc! z=@b4iXeUON?K@I6cdRbHB;bKho-NCF(-+Mf(mj96t7>073HoNRsI8`Fw(-od*Y-a0 zoJN@DY#-1#yYzia);|N+&KX%-YtFj)rgf*Qr{ZeqXlZHXrlb&7M@F|=n%?RA+jMGH zR{O>70Y{Qj8p8JO8_iGNa6Nmm?GAnWcE`%(IpY-FWN9-!cLj^(vx)$XELA;0hFVNq z1heRIuH9q>jgG~VkF#45Gz+I8J)S|B73_}Pb!>>h6zr5xHD7I2;+aHz4vA}X5_M=! z7AnQq<03rdb{QZr;WPr-O-3^%vn$vRUK#k!O!L@~143sh*s1CyNNFK)NW>L!`J5O# zF+;$P@POnbW{_#3WBVw;NWspeX{(IKv)OE1n~-ZEGkCC6D&_G7Jb{1%5FBcznMUj! zGZn~C^m0Vw6iO0Sny{E5h7-|SvS|gI4c4JPa84 ze7=Ok7jOhpp0hnzRjd20%~YR?Ku?|>vGQOppJy^XWkJy~GoJW+rUj(~_X$sfQsnwWGIs43}aAwL8w-Gb&Ku`|P&Ngu>byB|#BVz_(vN|jnv@;Sz`*GH6 z(&)foC=WN{CJ+b(nBk}JG+}shKu_(5S@}aCz+FH8Q|QNfIbw0>DvP$D*^E{VS&&fG>G{eX_$!55us8sM-qlfIY_8LD$pZ{0Tqdys8nW(M$9PAPyuo- z0eFN$Tp$$Tzz{|S91%YZ=O8c&bHrg%L=Y;LqM}fV6U9W50F{6kougu?Fop_2U?eOI z=7j3?QjQ1%$$}U#5eE;04SWnkq|=<$Rw45E#UGUdBIhFyUGnVPu2Fut+S0C1QzG0E0!_ zR9s)pMIjkH$576D9{3MT+Dwbh{J-%$f%daRlC;f2W=$j~>OaI$`uRN10{1g%K)0i4 za;EA}HuWDkd2hnT16vC@)5*UU|EPD=+meigLn;Vzv;i4{_R3EoGjPmt1VG2$B{UN; zXW*dU_7&UXdEyy~Y!HNrr4oaIg9u;=$Ua2Kkw{Tc3t<8QDwg7UL0|Uuvr`rWZ9_;r zG6UoQIA>5?j&p`W`tong)6v*6aVEL=0+_=Wf-0Ep{tM2@zAs1r%-k8)Ypt|cXM!ruq$k?U&vpL{ zU@yZ20>#ag<$0kyLwaR#x`;r`y>sA#1NSAb|HACc5~h&;!dGAR{e>O?^-Cu&r0+|) zUdr`C3cL{bWp=%k>xC3}A@IxW`oGEL@#K*kH-rECY~X=<-M33egNJc9{rI?O)+6S* zT=;fw7x;SB8lOh7Si@-MGvJ&dzz!w{(JFP!pi8bUUIFe~wibkfsbp1jq%QS9dlT%z z8a+gLvh`uXaj{3ymU(>s)CVagG5gmh6a}3uIJ>9G{it6}MM`J*!J5Q3x|(ao4{q!% vQkRu=n(Wzy554H~`L|Qne$e9zI9W3Jy3lE-Qms`xn2{GO;dABEP>XQTZq{4BzJrL?~Z-z253CcU}8 zaVd*N&EZ&D+uuxlvu&zV?%9NcGi($NjYjw_T5X@ud-vSzqZ@uWc4=!w=ZVU7yYq97 zd&ai^`bpM`9{cBB5kH3}G$R==Pg>DzXWP{?Coa-wN9O#cVdw8unokY$pORngJ5P2! zQa*pwl4$sZeQ@0RRqZ|n4d>6k>pF47k+ms7Eu(_(7~Jc;suOKUuZ3py*_?KX9O7uwNqG-lTZeTKWQkN)c3&r_}(y4G_e zbY+M}CN}A)bxAdoE>|yDlk)T@s?BjtDOaNQjXk_+SEm05MXO$;qDq5uhW|9>#W~Bz z)zI1GPL9aru}V0%L$`QV`2j^Wiq!9@fAM(d*byI^y5{XH#LoxqCbNab73b8q^L{QX zN7P5gq}Lg9YAi03KHGRn?ffFAZ1Ks0ox@7n?-ajmH_TLVdiQ|~pL}<)lKJ}Nj>!q7 zcDatVS*b;>=Q-0pYh9fbGP6E-bVY~{;>o+zQvY6kLDjG`%a$o?CxwpUJ!@EdwbkO; zQcYc+zfL%UtK8yVdL*WP`ne?k-?k=gE8DWgZ%(;~=!QePokF{~<`okWr;W!dl3Jen(oX-}xT5L~kHl}7HHod-ro{KBmWr`dN;x)(bIwi;KJJ_8k zJX?Ibu`4#X1mmfNs@?}|-5XkO&@MW-@%ZQnmLEvGti-}W26{_RaIZMq+{0^)}^5YNZraNT2ado=rf+9stUJ0Q!*xU9OHK(cxRK> z{>5I{j9bn51&S}mtXO$aW#2ThIW#v^=I z$>ZitanE1w@Vuxf`n4Q~564gQi21i$*DdFldfjVDUZ` zwl&G=T4q)I`e^R%5$KP5etT8>?H6O}?>W7!Nz3}!ruo&v1^U$UlS{KE)YRxwJ6|x= z+B(UkmwV-ds^Q9B%NZQZykMMk+QVLP=z#5>k@q>*FZ1V1OWvVsNe{O{=3Zi=1cW z#lUZNn$P1}Axx^A7atzQ_17CIF6xDP!BC(@vqZr2apcO3q#BC}2j7>!=XWjS$`-o(gx zJh0B~CkLxUpao3!D`3Y~dYU2m3NS!{KLEfNz_0{@1&~0>A1n`6!@~!pb##9eK|J{u z+`vb?V18QK5DA(IT=GEQLnUY>IAZuQ6s>AO5oy$bO2D;)tzxN2KqZxW!zw9GLa11Xu-4#^loAjSA(arMK!vE)YRXE* z?gQo*rI7OkUVY@&nJ6vJsP)D)P+l4xsW;OPz)DRT70cjk*bp&-ilvA|jCxCek>UrS zILb(aG-p{6*h?U=u8;&41UPY!ZkjYanc^FC$<_sSV=&+j2rbT52Vl0&18*>YBZV`1 zqf)Qe%6Y79T$a*WR9snqxnN;>!YXJ5QzTn){WUiWPv%>PGXA5$e_)DD(VKPu8_xsi z0E?fIG3$+~k;X{XB8p%h&GRtu08W%4x^v6<*`$l~& zNvpAjip#aO0SqVl!cXH%DAKwGAdY=YL<+7;ra-^#FSh&hnujDZB_vP*p+X=SmV#Ok ziU>%eRtX`TkVs**iWE`(i9Eng>(z`IH&TAdAP2xcgW|I8Gk0o#{!JTdjX8y4lM5CA z9YW9tm9PXuB^ct(>)Y-A-T!;s1D#&&jT0n93Pdo33PcDbm5Rj>DfJeS1VNHgXu zb?zZT3`H>k@=v%Y6rd=Dg5w31pla};f+2~D#37YXBt>ytBvGS%<@p!7hhnf8L&X1t zd!)Az5yAuxdBbXuoFdR*B{&K8QKG^@tENa1sv4SX|95gCC?-TO7`^YFjL#nb_e)X6 z9+2VT*x+K7u?HxoV7p|j$zafGDC5u~zTedT3vQ5ne*^t9@xib@X@9*T9UL(!Or%-& zNc9f^_A!KM2uermA7%PrNMBe6JtYB~`{uyY7kHTC4?NBFw=j@^f9I>eHU6C{xZEd7 z9xJ|2!u2Fvj}?K(0zb*FC*gXm2s{?}Np}5Z;c|R%A4KWEpAs{;?I~YZ@D{i!8m^ih z9KgB9UK$BKmv(`#rwt(qG`L=3*tg9YwVMS@+B1sqK>N$KPmOdNRxzRJ1u&&m1o$cA z8`?KW9qVDIVISYQSK_&D;^^1EN{Fel)qQ;GIH|fqLR)OzwG|W>QE~ABO5t?TvNr({wys^zw)>Zy6Ow!H@B4l4 zZ|<4ZQ4wLo?A`4t6v{A3c!&&q)65^L4ft=!$mpO@td$w^I8uffX*xZjRBLb=nXbcW zxJj*~P)yez6wEKEhp0J?N7gW_vYAPu<8gOM+xV=d%{A3qvBL6n&xr-tTB~+Mq%`f@8~X7S@__oo>61^=FQ;8nO@g1@xwk*>%p%_;PSvQn{=(K9m$$pSpPjKi z=`wfDs*k>)j>yS7GndQ=BS+^UcqbGq4iH^{Pk)SDveFvUs9Y|NEDwdC_G*; z|6W50TGUcjx<9b%;P-1}^Pai?tzu8?)vpRZka=*PQpe@OWOH=H!e;*GMb}!dwCp&) zag+a^jqYQm_n@HiPllp4iu-oe8%InFI>es8=_{40W$jP-qlwVOkkO72zT<3`WxYFm zc|@95GqYh^SI@b1PWo@_PAuQXB_l5^xzm|F@93A(`PI_to$m8wBdc0%OU|tEIw^5F zU)=e{#?)ywC#86${%Q?NnkhOy=N%O-VQ)o@&gK4I^jZDhhN7K&4=f!@F0RSEzOZtA z_N=t7EuWs+64aHEljgggJO10JnkLoO^y8;zu6>xWsIhWU*pSYJ-PT{;y?m_4|IEkj zMYn<;uIZ5(9*(*ERbtD9>mz;)vwHfZva|f-&icBe#YAvp&W(lPHow+kT^{XuSH2E) znz)!kv09-H4vvxp2S4vP&}ZA0PSE64LTvA{#2|TfnY@hZHe-Hq ztnJ2`r*=q!^P8IdD%?Du#(gfX5Vro~<^<|8FPc^xv4$yY>dBw}(I}_f?)HZ_Zao}B zgD1@5RgSlcJ+xLHFkWfcr?~k}7L}UlY3E zYG>rk2nhC^EnoL|<>xi_cNyOxN171YtZP0=5$q<-P< zKVEj&>q(o_)dhl%+?kYM11{PbB|++>WIl6&2d#$76NJtQ`aI<$H_ri}pnMQ68x}q@u(& zx0ZOr;^YUnI;<{4&8ZQbuJVRv@4lb9-q=NUc0SrVd+F4`4t;(DnZ1QV88J@{CSaU& zmKY^cmhKIb z9+MFvM>CR95k{Xn#Xi6!1_UWMiO@_b8m&QW3Zz?j#o)Jjn?}dgdOevOyyw^toR)O>+lGKS=EThrLG;*1QUd3XIL?RZ%VR1MN zfM6KXwIpI2z>T>mvuJXs8Kn_9-y0E%XKw zV@bdQ2L1p5ivvMI2E<`-M67}G;8ZH@m)08kq6p&2G9fw^n+dT}QU*yF$k4PG`d%tw zkb?&iONJYWR6UA^rr}z0+(4x|O{!s_&Qt?#-m=)O!IUgultuGEnJ|eos$a$&MwL26 zXOS?Y2O=@FAE!&zYb+QHW#Jk;1sGxg%m=>7F#TF z6^9ULs#&QdB#>^lSBw#;8WUT7@;CyHLV+q6JcNfZV5JCU2q7MifhdJSNQ7V_1y?kH zN}@H8h!(}oRDhhR20Snd350w$#=w+p9s@>1T!s)u1q?{Y<03F7#83And=Xp7huA_6kHdZenvLrXpv=vzY>3I> zSWYlh{2t&$K)IEnplocU)i3O-EEL%QArVD)4WZVv-1>_P^tK0qtiA(vwC)pB$-= zR4l|%^3^;q1NSq@z_c@v`gFn*njb9F()UVpt&N z@aVnS?#uq4;~tyO7s43AV{l;(mjUxoKIn1`X22LO65s*_j*EI*{SR~x7IPpmoBjWA zkH_UGaUL#U@L&wM2g4%Zo=5=l$QD3cwt~Zf1cS5jzh@5?bJ=1DdhT8T%lv$NUaJA- zCx}!k9$3u*<|m9;Vjd$gsXCoTjq3+j`SY&-54ZvHeG}&Q#0SE9rGp7wI+(#pWTa91 zO7$-R_A<;;qqx>Uyvp=}klwHixJCe*d-uTg1l)F5{nwqo9tQ>ZXa4ogjz3ccjrK;# zYsL3XxZZ^8wIc9Z;5XIvCS0!-f!6}RsjfdPT=p+snBZD)Yd3<|A=ElL6TC{XQOpbr zq4b#Fs&IWXpMWnrUHDuBh2l(_KUO!CUM8?1nwzsW^4D+JwOmngYTLqGkAbH%` z2b-&gXVV;m{eSA|c^p9XytCkKkB$Q$>#C{?w2qr7v9Xo5l$BIE61i`&V)O^TQ=Sm9 zC0kCf5&p0uq$$F|RqwFP&cr*l?+EXo{FUzKhMzuRH){graEz&`BiYEVL(6TidrxtU zbE&K@Elp@78qX^_KCE4Pt@H`<=}-qr^d+B(XKIg5JLw#e?<2|g`DK``_t+#)(>@IX PETl+6BSQ91Ph9pd_9v4B literal 0 HcmV?d00001 diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/cursors/select.png b/src/com/jpexs/decompiler/flash/gui/graphics/cursors/select.png new file mode 100644 index 0000000000000000000000000000000000000000..e52926e7c916671a66d3aba13379351a2a9d1529 GIT binary patch literal 6473 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 U#dvAD3fRg~hKvg?3!I+yZ&L>>M*si- literal 0 HcmV?d00001 diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/cursors/shear_x.png b/src/com/jpexs/decompiler/flash/gui/graphics/cursors/shear_x.png new file mode 100644 index 0000000000000000000000000000000000000000..0d9c27e5b51859d4964240b9e927cada6c82d719 GIT binary patch literal 5438 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 literal 0 HcmV?d00001 diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/cursors/shear_y.png b/src/com/jpexs/decompiler/flash/gui/graphics/cursors/shear_y.png new file mode 100644 index 0000000000000000000000000000000000000000..c66d7ebbf0ae31286b5ef5a780c7778212c66087 GIT binary patch literal 5696 zcmeHLc~leE8V_{=5k*mv0&0ke+nX$tg(N0w0tlf75CWxuPbM>yFhCYE13?fM>PlS- z3U0Jo70R>L3av#BwzyU*R9kDGZM7VwN>v1*Qbon`?gWJJ>a)*t+W*Ws$>iSq`+nd4 z&74^m5f(hc+0&W9V2n_O1V(}@lm0pl1;1ogRtJMINSdXIr6Lh4(`+&6h*X?OEjHs! zJd@Bd7@0q|6d4Mx`?;*{CiA=U}toC#HhsoWuM_xBSs+=19i?U)YH0}wtB|P}V8OA#!&z3{GDsDY-zb*c8>E(43 zD&wPDv-iKhWdGtv6UKhiJi6KG-YBc+eON|0xv%W_$_4$bnI_~!P5_enC#dLb>WcTsp=VxDSQg+5)>6~{CqIN03kEu52`u*IvMT>Seoi-?&g$at^axb}VcJ}(c zO|6_|$hnNwJ2tQR_h(+6Kin_bJ7tgZ#1B7}O3yqJ6igFa=CM^!KK z+FPUfjTPETb}YH{&H0-TPhV`!>^ic4S?-QIfxmMWWPi}SDC^Sa-Z_#-mX7WW-!a`s zum@8g;&tQKr_a?h7!E6mfPe^9K)};30=-k5lP?cBIoJF2qNozqIK`~BVyrATQK30i zt|@nN4~>5>+Hv!1HM>;-1&xiRY<64Pi5%VS42b{8n$@^Ibw zxV^RE=hg{K!HoIBYCnhQkJoEteme34?bUHhotzTA2f6Wo3(VpTI^Uk|I%&~emaOja zm0f$ozuf84w(it|QZM)Rq3w4{OUQ<)Bg+f+joLLP+=Egy1|O_r*QBwf|7fd!J74wH zm^EupYaQz*)Q7GPl?8amXg2)1>Oh_IFVMNwBE+KAWd{Er>?;l}K{lfKFNfVzMG?)I2vbL1VCf-BVW zZ4Ab!YywQRSoM4 zoU4McG_(Pxk!4ROXkADFA zX}oN<*y1V;G@Ourn*cl*Un>#ALLQ{!`wJie26G`Xic27WEexYV1Q(!~Jw>>M z0G)uO+FM0aVStJ+z_=(Z6hm5nSOf`lLVpMm>O_!4q654-L{M1@<8&sF~nU=PE50>zD_>3OExLwdqucb^3|_w0fDH+UT2^gR#sHUT{+{>D#l zbN!7P0QE~HFBIRG;d&Xa7mC0OfnQeF%W%C=1YQXIvbz4Va5?wCQNWGhvDOOS7Z?>S z$>3eXQ0;5MfsAhYwF7_Zl6&AX%pCFt$zZrq^w*(5Hz^ZrI#McikmHR(uZ-|>gp0;c z1Y2fRph6RSvZaVO@)*-)5Zc{+zBO{6b0Pelu66@bJIs6c?G?V!dDT@{+=_=Y4$L}U zxhE?3gCSc7&w-KrtA5*tFnB9!cAAFl@%hBr*EBb@;9A+GR~X!R?2N$yEYA~L2LDnO l{&}swYxs^*Vn=Bj=`ecOESCp}&o!W%p$ZBMJg7{}{SVR5IG+Fj literal 0 HcmV?d00001