From eb6bfbc29cde036f83c80c1e963d32b15f16ee20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Fri, 12 Aug 2016 21:35:26 +0200 Subject: [PATCH] ImagePanel - no render image Nullpointer fix in some cases --- .../decompiler/flash/gui/ImagePanel.java | 2865 +++++++++-------- 1 file changed, 1434 insertions(+), 1431 deletions(-) diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index b2c098ec7..4299dbb96 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -1,1431 +1,1434 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.LocalDataArea; -import com.jpexs.decompiler.flash.action.Stage; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.ecma.Undefined; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.gui.player.MediaDisplay; -import com.jpexs.decompiler.flash.gui.player.MediaDisplayListener; -import com.jpexs.decompiler.flash.gui.player.Zoom; -import com.jpexs.decompiler.flash.tags.DefineButtonSoundTag; -import com.jpexs.decompiler.flash.tags.DoActionTag; -import com.jpexs.decompiler.flash.tags.base.BoundedTag; -import com.jpexs.decompiler.flash.tags.base.ButtonTag; -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.tags.base.SoundTag; -import com.jpexs.decompiler.flash.tags.base.TextTag; -import com.jpexs.decompiler.flash.timeline.DepthState; -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.RECT; -import com.jpexs.decompiler.flash.types.SOUNDINFO; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Cache; -import com.jpexs.helpers.SerializableImage; -import com.jpexs.helpers.Stopwatch; -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Cursor; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.Transparency; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionAdapter; -import java.awt.event.MouseMotionListener; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.awt.image.VolatileImage; -import java.io.IOException; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.sound.sampled.LineUnavailableException; -import javax.sound.sampled.UnsupportedAudioFileException; -import javax.swing.JLabel; -import javax.swing.JPanel; - -/** - * - * @author JPEXS - */ -public final class ImagePanel extends JPanel implements MediaDisplay { - - private static final Logger logger = Logger.getLogger(ImagePanel.class.getName()); - - private final List listeners = new ArrayList<>(); - - private Timelined timelined; - - private boolean stillFrame = false; - - private Timer timer; - - private int frame = -1; - - private boolean loop; - - private LocalDataArea lda; - - private boolean zoomAvailable = false; - - private SWF swf; - - private boolean loaded; - - private int mouseButton; - - private final JLabel debugLabel = new JLabel("-"); - - private Point cursorPosition = null; - - private MouseEvent lastMouseEvent = null; - - private final List soundPlayers = new ArrayList<>(); - - private final Cache displayObjectCache = Cache.getInstance(false, false, "displayObject"); - - private final IconPanel iconPanel; - - private int time = 0; - - private int selectedDepth = -1; - - private Zoom zoom = new Zoom(); - - private final Object delayObject = new Object(); - - private boolean drawReady; - - private final int drawWaitLimit = 50; // ms - - private TextTag textTag; - - private TextTag newTextTag; - - private int msPerFrame; - - private final boolean lowQuality = false; - - private final double LQ_FACTOR = 2; - - public synchronized void selectDepth(int depth) { - if (depth != selectedDepth) { - this.selectedDepth = depth; - } - - hideMouseSelection(); - } - - public void fireMediaDisplayStateChanged() { - for (MediaDisplayListener l : listeners) { - l.mediaDisplayStateChanged(this); - } - } - - @Override - public void addEventListener(MediaDisplayListener listener) { - listeners.add(listener); - } - - @Override - public void removeEventListener(MediaDisplayListener listener) { - listeners.remove(listener); - } - - private class IconPanel extends JPanel { - - private SerializableImage _img; - - private Rectangle _rect = null; - - private ButtonTag mouseOverButton = null; - - private boolean autoFit = false; - - private boolean allowMove = true; - - private Point dragStart = null; - - private Point offsetPoint = new Point(0, 0); - - private synchronized SerializableImage getImg() { - return _img; - } - - public synchronized Rectangle getRect() { - return _rect; - } - - public boolean hasAllowMove() { - return allowMove; - } - - VolatileImage renderImage; - - public void render() { - SerializableImage img = getImg(); - Rectangle rect = getRect(); - if (img == null) { - return; - } - Graphics2D g2 = null; - do { - - int valid = renderImage.validate(View.getDefaultConfiguration()); - - if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { - renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); - } - - try { - g2 = renderImage.createGraphics(); - g2.setPaint(View.transparentPaint); - g2.fill(new Rectangle(0, 0, getWidth(), getHeight())); - g2.setComposite(AlphaComposite.SrcOver); - g2.setPaint(View.getSwfBackgroundColor()); - g2.fill(new Rectangle(0, 0, getWidth(), getHeight())); - - g2.setComposite(AlphaComposite.SrcOver); - if (rect != null) { - g2.drawImage(img.getBufferedImage(), rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, 0, 0, img.getWidth(), img.getHeight(), null); - } - } finally { - if (g2 != null) { - g2.dispose(); - } - } - - } while (renderImage.contentsLost()); - } - - public IconPanel() { - addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); - if (_img != null) { - calcRect(); - render(); - } - repaint(); - } - - }); - addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON1) { - dragStart = e.getPoint(); - } - } - - @Override - public void mouseReleased(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON1) { - dragStart = null; - } - } - - }); - addMouseMotionListener(new MouseMotionAdapter() { - @Override - public void mouseDragged(MouseEvent e) { - if (dragStart != null && allowMove) { - Point dragEnd = e.getPoint(); - Point delta = new Point(dragEnd.x - dragStart.x, dragEnd.y - dragStart.y); - offsetPoint.x += delta.x; - offsetPoint.y += delta.y; - dragStart = dragEnd; - repaint(); - } - } - - }); - } - - public void setAutoFit(boolean autoFit) { - this.autoFit = autoFit; - repaint(); - } - - public synchronized BufferedImage getLastImage() { - if (_img == null) { - return null; - } - return _img.getBufferedImage(); - } - - public synchronized void setImg(SerializableImage img) { - this._img = img; - if (img != null) { - calcRect(); - render(); - } - repaint(); - } - - public synchronized Point toImagePoint(Point p) { - if (_img == null) { - return null; - } - - return new Point((p.x - _rect.x) * _img.getWidth() / _rect.width, (p.y - _rect.y) * _img.getHeight() / _rect.height); - } - - private void setAllowMove(boolean allowMove) { - this.allowMove = allowMove; - if (!allowMove) { - offsetPoint = new Point(); - } - } - - private synchronized void calcRect() { - if (_img != null) { - int w1 = (int) (_img.getWidth() * (lowQuality ? LQ_FACTOR : 1)); - int h1 = (int) (_img.getHeight() * (lowQuality ? LQ_FACTOR : 1)); - - int w2 = getWidth(); - int h2 = getHeight(); - - int w; - int h; - if (autoFit) { - if (w1 <= w2 && h1 <= h2) { - w = w1; - h = h1; - } else { - - h = h1 * w2 / w1; - if (h > h2) { - w = w1 * h2 / h1; - h = h2; - } else { - w = w2; - } - } - } else { - w = w1; - h = h1; - } - - setAllowMove(h > h2 || w > w2); - _rect = new Rectangle(getWidth() / 2 - w / 2 + offsetPoint.x, getHeight() / 2 - h / 2 + offsetPoint.y, w, h); - } else { - _rect = null; - } - } - - @Override - protected void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D) g; - - if (renderImage != null) { - calcRect(); - if (renderImage.validate(View.getDefaultConfiguration()) != VolatileImage.IMAGE_OK) { - renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); - render(); - } - - if (renderImage != null) { - g2d.drawImage(renderImage, 0, 0, null); - } - } - g2d.setColor(Color.red); - - DecimalFormat df = new DecimalFormat(); - df.setMaximumFractionDigits(2); - df.setMinimumFractionDigits(0); - df.setGroupingUsed(false); - - float frameLoss = 100 - (getFpsIs() / fpsShouldBe * 100); - - if (Configuration._debugMode.get()) { - g2d.drawString("frameLoss:" + df.format(frameLoss) + "%", 20, 20); - } - } - } - - @Override - public void setBackground(Color bg) { - if (iconPanel != null) { - iconPanel.setBackground(bg); - } - super.setBackground(bg); - } - - @Override - public synchronized void addMouseListener(MouseListener l) { - iconPanel.addMouseListener(l); - } - - @Override - public synchronized void removeMouseListener(MouseListener l) { - iconPanel.removeMouseListener(l); - } - - @Override - public synchronized void addMouseMotionListener(MouseMotionListener l) { - iconPanel.addMouseMotionListener(l); - } - - @Override - public synchronized void removeMouseMotionListener(MouseMotionListener l) { - iconPanel.removeMouseMotionListener(l); - } - - private void updatePos(Timelined timelined, MouseEvent lastMouseEvent, Timer thisTimer) { - if (timelined != null) { - - BoundedTag bounded = (BoundedTag) timelined; - RECT rect = bounded.getRect(); - int width = rect.getWidth(); - double scale = 1.0; - /*if (width > swf.displayRect.getWidth()) { - scale = (double) swf.displayRect.getWidth() / (double) width; - }*/ - Matrix m = Matrix.getTranslateInstance(-rect.Xmin, -rect.Ymin); - m.scale(scale); - - Point p = lastMouseEvent == null ? null : lastMouseEvent.getPoint(); - - synchronized (ImagePanel.class) { - if (timer == thisTimer) { - cursorPosition = p; - } - } - } - } - - private void showSelectedName() { - if (selectedDepth > -1 && frame > -1) { - DepthState ds = timelined.getTimeline().getFrame(frame).layers.get(selectedDepth); - if (ds != null) { - CharacterTag cht = timelined.getTimeline().swf.getCharacter(ds.characterId); - if (cht != null) { - debugLabel.setText(cht.getName()); - } - } - } - } - - public void hideMouseSelection() { - if (selectedDepth > -1) { - showSelectedName(); - } else { - debugLabel.setText(" - "); - } - } - - public ImagePanel() { - super(new BorderLayout()); - //iconPanel.setHorizontalAlignment(JLabel.CENTER); - setOpaque(true); - setBackground(View.getDefaultBackgroundColor()); - - loop = true; - iconPanel = new IconPanel(); - //labelPan.add(label, new GridBagConstraints()); - add(iconPanel, BorderLayout.CENTER); - add(debugLabel, BorderLayout.NORTH); - iconPanel.addMouseListener(new MouseAdapter() { - - @Override - public void mouseEntered(MouseEvent e) { - synchronized (ImagePanel.class) { - lastMouseEvent = e; - redraw(); - } - } - - @Override - public void mouseExited(MouseEvent e) { - synchronized (ImagePanel.class) { - lastMouseEvent = null; - hideMouseSelection(); - redraw(); - } - } - - @Override - public void mousePressed(MouseEvent e) { - synchronized (ImagePanel.class) { - mouseButton = e.getButton(); - lastMouseEvent = e; - redraw(); - ButtonTag button = iconPanel.mouseOverButton; - if (button != null) { - DefineButtonSoundTag sounds = button.getSounds(); - if (sounds != null && sounds.buttonSoundChar2 != 0) { // OverUpToOverDown - playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar2), sounds.buttonSoundInfo2, timer); - } - } - } - } - - @Override - public void mouseReleased(MouseEvent e) { - synchronized (ImagePanel.class) { - mouseButton = 0; - lastMouseEvent = e; - redraw(); - ButtonTag button = iconPanel.mouseOverButton; - if (button != null) { - DefineButtonSoundTag sounds = button.getSounds(); - if (sounds != null && sounds.buttonSoundChar3 != 0) { // OverDownToOverUp - playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar3), sounds.buttonSoundInfo3, timer); - } - } - } - } - - }); - iconPanel.addMouseMotionListener(new MouseMotionAdapter() { - @Override - public void mouseMoved(MouseEvent e) { - synchronized (ImagePanel.class) { - lastMouseEvent = e; - redraw(); - } - } - - @Override - public void mouseDragged(MouseEvent e) { - synchronized (ImagePanel.class) { - lastMouseEvent = e; - redraw(); - } - } - - }); - } - - private synchronized void redraw() { - if (timer == null && timelined != null) { - startTimer(timelined.getTimeline(), false); - } - } - - @Override - public synchronized void zoom(Zoom zoom) { - boolean modified = this.zoom.value != zoom.value || this.zoom.fit != zoom.fit; - if (modified) { - this.zoom = zoom; - displayObjectCache.clear(); - redraw(); - if (textTag != null) { - setText(textTag, newTextTag); - } - - fireMediaDisplayStateChanged(); - } - } - - @Override - public synchronized BufferedImage printScreen() { - return iconPanel.getLastImage(); - } - - @Override - public synchronized double getZoomToFit() { - if (timelined != null) { - RECT bounds = timelined.getRect(); - double w1 = bounds.getWidth() / SWF.unitDivisor; - double h1 = bounds.getHeight() / SWF.unitDivisor; - - double w2 = getWidth(); - double h2 = getHeight(); - - double w; - double h; - h = h1 * w2 / w1; - if (h > h2) { - w = w1 * h2 / h1; - } else { - w = w2; - } - - if (w1 <= Double.MIN_NORMAL) { - return 1.0; - } - - return (double) w / (double) w1; - } - - return 1; - } - - @Override - public synchronized boolean zoomAvailable() { - return zoomAvailable; - } - - public void setTimelined(final Timelined drawable, final SWF swf, int frame) { - Stage stage = new Stage(drawable) { - @Override - public void callFrame(int frame) { - executeFrame(frame); - } - - @Override - public Object callFunction(long functionAddress, long functionLength, List args, Map regNames, Object thisObj) { - try { - SWFInputStream sis = new SWFInputStream(swf, swf.uncompressedData, functionAddress, (int) (functionAddress + functionLength)); - return execute(sis); - } catch (IOException ex) { - Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex); - } - return Undefined.INSTANCE; - } - - @Override - public int getCurrentFrame() { - return ImagePanel.this.getCurrentFrame(); - } - - @Override - public int getTotalFrames() { - return ImagePanel.this.getTotalFrames(); - } - - @Override - public void gotoFrame(int frame) { - ImagePanel.this.pause(); - ImagePanel.this.gotoFrame(frame); - } - - @Override - public void gotoLabel(String label) { - //TODO - } - - @Override - public void pause() { - ImagePanel.this.pause(); - } - - @Override - public void play() { - ImagePanel.this.play(); - } - - @Override - public void trace(Object... val) { - for (Object o : val) { - System.out.println("trace:" + o.toString()); - } - } - - }; - lda = new LocalDataArea(stage); - synchronized (ImagePanel.class) { - stopInternal(); - if (drawable instanceof ButtonTag) { - frame = ButtonTag.FRAME_UP; - } - - displayObjectCache.clear(); - this.timelined = drawable; - this.swf = swf; - zoomAvailable = true; - timer = null; - if (frame > -1) { - this.frame = frame; - this.stillFrame = true; - } else { - this.frame = 0; - this.stillFrame = false; - } - - loaded = true; - - if (drawable.getTimeline().getFrameCount() == 0) { - clearImagePanel(); - return; - } - - time = 0; - drawReady = false; - redraw(); - play(); - } - - synchronized (delayObject) { - try { - delayObject.wait(drawWaitLimit); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - synchronized (ImagePanel.class) { - if (!drawReady) { - clearImagePanel(); - } - } - - fireMediaDisplayStateChanged(); - } - - public synchronized void setImage(SerializableImage image) { - lda = null; - setBackground(View.getSwfBackgroundColor()); - clear(); - - timelined = null; - loaded = true; - stillFrame = true; - zoomAvailable = false; - iconPanel.setImg(image); - drawReady = true; - - fireMediaDisplayStateChanged(); - } - - public synchronized void setText(TextTag textTag, TextTag newTextTag) { - setBackground(View.getSwfBackgroundColor()); - clear(); - - lda = null; - timelined = null; - loaded = true; - stillFrame = true; - zoomAvailable = true; - - this.textTag = textTag; - this.newTextTag = newTextTag; - - double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; - - RECT rect = textTag.getRect(); - int width = (int) (rect.getWidth() * zoomDouble); - int height = (int) (rect.getHeight() * zoomDouble); - SerializableImage image = new SerializableImage((int) (width / SWF.unitDivisor) + 1, - (int) (height / SWF.unitDivisor) + 1, SerializableImage.TYPE_INT_ARGB); - image.fillTransparent(); - Matrix m = Matrix.getTranslateInstance(-rect.Xmin * zoomDouble, -rect.Ymin * zoomDouble); - m.scale(zoomDouble); - textTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFFC0C0C0)); - - if (newTextTag != null) { - newTextTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFF000000)); - } - - iconPanel.setImg(image); - drawReady = true; - - fireMediaDisplayStateChanged(); - } - - private synchronized void clearImagePanel() { - iconPanel.setImg(null); - } - - @Override - public synchronized int getCurrentFrame() { - return frame; - } - - @Override - public synchronized int getTotalFrames() { - if (timelined == null) { - return 0; - } - if (stillFrame) { - return 0; - } - return timelined.getTimeline().getFrameCount(); - } - - @Override - public void pause() { - stopInternal(); - redraw(); - } - - @Override - public void stop() { - stopInternal(); - rewind(); - redraw(); - } - - @Override - public void close() throws IOException { - stopInternal(); - } - - private void stopAllSounds() { - for (int i = soundPlayers.size() - 1; i >= 0; i--) { - SoundTagPlayer pl = soundPlayers.get(i); - pl.close(); - } - soundPlayers.clear(); - } - - private void clear() { - if (timer != null) { - timer.cancel(); - timer = null; - fireMediaDisplayStateChanged(); - } - - textTag = null; - newTextTag = null; - displayObjectCache.clear(); - } - - private void nextFrame(Timer thisTimer, final int cnt, final int timeShouldBe) { - drawFrame(thisTimer, true); - - synchronized (ImagePanel.class) { - if (timelined != null && timer == thisTimer) { - int frameCount = timelined.getTimeline().getFrameCount(); - - int oldFrame = frame; - for (int i = 0; i < cnt; i++) { - if (!stillFrame && frameCount > 0) { - frame = (frame + 1) % frameCount; - } - - if (!stillFrame && frame == frameCount - 1 && !loop) { - stopInternal(); - return; - } - - if (i < cnt - 1) { //skip not displayed frames, do not display, only play sounds, etc. - drawFrame(thisTimer, false); - } - } - if (frame != oldFrame) { - if (frame == 0) { - stopAllSounds(); - } - time = 0; - } else { - time = timeShouldBe; - } - } - } - - fireMediaDisplayStateChanged(); - } - - private static SerializableImage getFrame(SWF swf, int frame, int time, Timelined drawable, RenderContext renderContext, int selectedDepth, double zoom) { - Timeline timeline = drawable.getTimeline(); - //int mouseButton = renderContext.mouseButton; - //Point cursorPosition = renderContext.cursorPosition; - //String key = "drawable_" + frame + "_" + drawable.hashCode() + "_" + mouseButton + "_depth" + selectedDepth + "_" + (cursorPosition == null ? "out" : cursorPosition.hashCode()) + "_" + zoom + "_" + timeline.fontFrameNum; - SerializableImage img; - //SerializableImage img = swf.getFromCache(key); - //if (img == null) { - //boolean shouldCache = timeline.isSingleFrame(frame); - RECT rect = drawable.getRect(); - - int width = (int) (rect.getWidth() * zoom); - int height = (int) (rect.getHeight() * zoom); - SerializableImage image = new SerializableImage((int) Math.ceil(width / SWF.unitDivisor), - (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); - 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) { - ds = timeline.getFrame(frame).layers.get(selectedDepth); - } - - 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; - Shape outline = dt.getOutline(dframe, time, ds.ratio, renderContext, Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m.concatenate(new Matrix(ds.matrix))), true); - Rectangle bounds = outline.getBounds(); - gg.setStroke(new BasicStroke(2.0f, - BasicStroke.CAP_BUTT, - BasicStroke.JOIN_MITER, - 10.0f, new float[]{10.0f}, 0.0f)); - gg.setPaint(Color.red); - gg.draw(bounds); - } - } - } - - img = image; - - /*if (shouldCache) { - swf.putToCache(key, img); - }*/ - //} - return img; - } - - private Object execute(SWFInputStream sis) throws IOException { - if (!Configuration.internalFlashViewerExecuteAs12.get()) { - return Undefined.INSTANCE; - } - if (lda == null) { - return Undefined.INSTANCE; - } - long ip = sis.getPos(); - //System.err.println("============="); - Action a; - while ((a = sis.readAction()) != null) { - int actionLengthWithHeader = a.getTotalActionLength(); - a.setAddress(ip); - a.execute(lda); - /*System.err.print("" + a + ", stack: ["); - for (Object o : lda.stack) { - System.err.print("" + o + ","); - } - System.err.println("]");*/ - if (lda.returnValue != null) { - return lda.returnValue; - } - if (lda.jump != null) { - ip = lda.jump; - lda.jump = null; - } else { - ip += actionLengthWithHeader; - } - sis.seek(ip); - } - return Undefined.INSTANCE; - } - - private void executeFrame(int frame) { - if (!Configuration.internalFlashViewerExecuteAs12.get()) { - return; - } - if (timelined == null) { - return; - } - Frame f = timelined.getTimeline().getFrame(frame); - List actions = f.actions; - if (lda != null) { - lda.clear(); - } - for (DoActionTag src : actions) { - try { - ByteArrayRange actionBytes = src.getActionBytes(); - int prevLength = actionBytes.getPos(); - SWFInputStream rri = new SWFInputStream(swf, actionBytes.getArray(), 0, prevLength + actionBytes.getLength()); - if (prevLength != 0) { - rri.seek(prevLength); - } - execute(rri); - } catch (IOException ex) { - Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex); - } - - } - } - - private void drawFrame(Timer thisTimer, boolean display) { - Timelined timelined; - MouseEvent lastMouseEvent; - int frame; - int time; - Point cursorPosition; - int mouseButton; - int selectedDepth; - Zoom zoom; - SWF swf; - - synchronized (ImagePanel.class) { - timelined = this.timelined; - lastMouseEvent = this.lastMouseEvent; - } - - synchronized (ImagePanel.class) { - frame = this.frame; - time = this.time; - cursorPosition = this.cursorPosition; - if (cursorPosition != null) { - cursorPosition = iconPanel.toImagePoint(cursorPosition); - } - - mouseButton = this.mouseButton; - selectedDepth = this.selectedDepth; - zoom = this.zoom; - swf = this.swf; - } - - if (timelined == null) { - return; - } - - 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<>(); - - SerializableImage img; - try { - Timeline timeline = timelined.getTimeline(); - if (frame >= timeline.getFrameCount()) { - return; - } - - double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; - if (lowQuality) { - zoomDouble /= LQ_FACTOR; - } - updatePos(timelined, lastMouseEvent, thisTimer); - - Matrix mat = new Matrix(); - mat.translateX = swf.displayRect.Xmin; - mat.translateY = swf.displayRect.Ymin; - - img = null; - if (display) { - Stopwatch sw = Stopwatch.startNew(); - img = getFrame(swf, frame, time, timelined, renderContext, selectedDepth, zoomDouble); - sw.stop(); - if (sw.getElapsedMilliseconds() > 100) { - logger.log(Level.WARNING, "Slow rendering. {0}. frame, time={1}, {2}ms", new Object[]{frame, time, sw.getElapsedMilliseconds()}); - } - - if (renderContext.borderImage != null) { - img = renderContext.borderImage; - } - } - - List sounds = new ArrayList<>(); - List soundClasses = new ArrayList<>(); - timeline.getSounds(frame, time, renderContext.mouseOverButton, mouseButton, sounds, soundClasses); - for (int cid : swf.getCharacters().keySet()) { - CharacterTag c = swf.getCharacter(cid); - for (String cls : soundClasses) { - if (cls.equals(c.getClassName())) { - sounds.add(cid); - } - } - } - - for (int sndId : sounds) { - CharacterTag c = swf.getCharacter(sndId); - if (c instanceof SoundTag) { - SoundTag st = (SoundTag) c; - playSound(st, null, thisTimer); - } - } - executeFrame(frame); - } catch (Throwable ex) { - // swf was closed during the rendering probably - return; - } - if (display) { - - StringBuilder ret = new StringBuilder(); - - if (cursorPosition != null) { - ret.append(" [").append(cursorPosition.x).append(",").append(cursorPosition.y).append("] : "); - } - - boolean handCursor = renderContext.mouseOverButton != null; - boolean first = true; - for (int i = renderContext.stateUnderCursor.size() - 1; i >= 0; i--) { - DepthState ds = renderContext.stateUnderCursor.get(i); - if (!first) { - ret.append(", "); - } - - first = false; - CharacterTag c = swf.getCharacter(ds.characterId); - ret.append(c.toString()); - } - - if (first) { - ret.append(" - "); - } - - ButtonTag lastMouseOverButton; - synchronized (ImagePanel.class) { - if (timer == thisTimer) { - iconPanel.setImg(img); - 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 (lastMouseOverButton != renderContext.mouseOverButton) { - ButtonTag b = renderContext.mouseOverButton; - if (b != null) { - // New mouse entered - DefineButtonSoundTag sounds = b.getSounds(); - if (sounds != null && sounds.buttonSoundChar1 != 0) { // IdleToOverUp - playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar1), sounds.buttonSoundInfo1, timer); - } - } - - b = lastMouseOverButton; - if (b != null) { - // Old mouse leave - DefineButtonSoundTag sounds = b.getSounds(); - if (sounds != null && sounds.buttonSoundChar0 != 0) { // OverUpToIdle - playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar0), sounds.buttonSoundInfo0, timer); - } - } - } - - drawReady = true; - synchronized (delayObject) { - delayObject.notify(); - } - } - } - } - } - - private void playSound(SoundTag st, SOUNDINFO soundInfo, Timer thisTimer) { - final SoundTagPlayer sp; - try { - int loopCount = 1; - if (soundInfo != null && soundInfo.hasLoops) { - loopCount = Math.max(1, soundInfo.loopCount); - } - - sp = new SoundTagPlayer(st, loopCount, false); - sp.addEventListener(new MediaDisplayListener() { - - @Override - public void mediaDisplayStateChanged(MediaDisplay source) { - } - - @Override - public void playingFinished(MediaDisplay source) { - synchronized (ImagePanel.class) { - sp.close(); - soundPlayers.remove(sp); - } - } - }); - - synchronized (ImagePanel.class) { - if (timer != null && timer == thisTimer) { - soundPlayers.add(sp); - sp.play(); - } else { - sp.close(); - } - } - } catch (LineUnavailableException | IOException | UnsupportedAudioFileException ex) { - logger.log(Level.SEVERE, "Error during playing sound", ex); - } - } - - public synchronized void clearAll() { - stopInternal(); - clearImagePanel(); - timelined = null; - swf = null; - - fireMediaDisplayStateChanged(); - } - - private synchronized void stopInternal() { - clear(); - stopAllSounds(); - } - - @Override - public synchronized void play() { - stopInternal(); - if (timelined != null) { - Timeline timeline = timelined.getTimeline(); - if (!stillFrame && frame == timeline.getFrameCount() - 1) { - frame = 0; - } - - startTimer(timeline, true); - } - } - - private synchronized void setMsPerFrame(int val) { - this.msPerFrame = val; - } - - private synchronized int getMsPerFrame() { - return this.msPerFrame; - } - - private long startRun = 0L; - - private final long startDrop = 0L; - - private int skippedFrames = 0; - - private float fpsShouldBe = 0; - - private float fpsIs = 0; - - private Timer fpsTimer; - - private int startFrame = 0; - - private synchronized void setFpsIs(float val) { - fpsIs = val; - } - - private synchronized float getFpsIs() { - return fpsIs; - } - - private synchronized void setSkippedFrames(int val) { - skippedFrames = val; - } - - private synchronized void addSkippedFrames(int val) { - skippedFrames += val; - } - - private synchronized int getSkippedFrames() { - return skippedFrames; - } - - private synchronized int getAndResetSkippedFrames() { - int ret = skippedFrames; - skippedFrames = 0; - return ret; - } - - private void scheduleTask(boolean singleFrame, long msDelay) { - TimerTask task = new TimerTask() { - public final Timer thisTimer = timer; - - public final boolean isSingleFrame = singleFrame; - - private long lastRun = 0L; - - @Override - public void run() { - try { - synchronized (ImagePanel.class) { - if (timer != thisTimer) { - return; - } - } - lastRun = System.currentTimeMillis(); - int curFrame = frame; - long delay = getMsPerFrame(); - if (isSingleFrame) { - drawFrame(thisTimer, true); - synchronized (ImagePanel.class) { - thisTimer.cancel(); - if (timer == thisTimer) { - timer = null; - } - } - - fireMediaDisplayStateChanged(); - } else { - //Time before drawing current frame - long frameTimeMsIs = System.currentTimeMillis(); - //Total number of frames in this timeline - int frameCount = timelined.getTimeline().getFrameCount(); - //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 - int frameOverMaxShouldBeNow = startFrame + ticksFromStart; - //Apply maximum frames repating, this is actual frame which should be drawed now - int frameShouldBeNow = frameOverMaxShouldBeNow % frameCount; - - //How many frames are there between last displayed frame and now. For perfect display(=no framedrop), value should be 1 - int skipFrames = frameShouldBeNow - curFrame; - //It is negative for some reason, this will display older frames. Add frameCount to stay in modulu framecount. - if (skipFrames < 0) { - skipFrames += frameCount; - } - //Change for more than 1 frame - if (skipFrames > 1) { - addSkippedFrames(skipFrames - 1); //drop those frames, draw only last one - } - //Frame "time" - ticks in current frame - int currentFrameTicks = 0; - if (frameCount == 1) { //We have only one frame, so the ticks on that frame equal ticks on whole timeline - currentFrameTicks = ticksFromStart; - } - nextFrame(thisTimer, skipFrames, currentFrameTicks); - - long afterDrawFrameTimeMsIs = System.currentTimeMillis(); - - int nextFrameOverMax = frameOverMaxShouldBeNow; - while (delay < 0) { //while the frame time already passed - nextFrameOverMax++; - long nextFrameOverMaxTimeMsShouldBe = startRun + getMsPerFrame() * nextFrameOverMax; - delay = nextFrameOverMaxTimeMsShouldBe - afterDrawFrameTimeMsIs; - } - } - //schedule next run of the task - scheduleTask(isSingleFrame, delay); - - } catch (Exception ex) { - logger.log(Level.SEVERE, "Frame drawing error", ex); - } - } - }; - if (timer != null) { - timer.schedule(task, msDelay); - } - } - - private synchronized void startTimer(Timeline timeline, boolean playing) { - - startRun = System.currentTimeMillis(); - startFrame = frame; - float frameRate = timeline.frameRate; - setMsPerFrame(frameRate == 0 ? 1000 : (int) (1000.0 / frameRate)); - final boolean singleFrame = !playing - || (stillFrame && timeline.isSingleFrame(frame)) - || (!stillFrame && timeline.getRealFrameCount() <= 1 && timeline.isSingleFrame()); - - if (fpsTimer == null) { - fpsTimer = new Timer(); - fpsTimer.schedule(new TimerTask() { - @Override - public void run() { - float skipped = getAndResetSkippedFrames(); - setFpsIs(fpsShouldBe - skipped); - } - }, 1000, 1000); - } - timer = new Timer(); - fpsShouldBe = timeline.frameRate; - fpsIs = fpsShouldBe; - scheduleTask(singleFrame, 0); - - } - - @Override - public synchronized void rewind() { - frame = 0; - fireMediaDisplayStateChanged(); - } - - @Override - public synchronized boolean isPlaying() { - if (timelined == null || stillFrame) { - return false; - } - - return (timelined.getTimeline().getFrameCount() <= 1) || (timer != null); - } - - @Override - public void setLoop(boolean loop) { - this.loop = loop; - } - - @Override - public synchronized void gotoFrame(int frame) { - if (timelined == null) { - return; - } - Timeline timeline = timelined.getTimeline(); - if (frame >= timeline.getFrameCount()) { - return; - } - if (frame < 0) { - return; - } - - this.frame = frame; - stopInternal(); - redraw(); - fireMediaDisplayStateChanged(); - } - - @Override - public synchronized float getFrameRate() { - if (timelined == null) { - return 1; - } - if (stillFrame) { - return 1; - } - return timelined.getTimeline().frameRate; - } - - @Override - public synchronized boolean isLoaded() { - return loaded; - } - - @Override - public boolean loopAvailable() { - return false; - } - - @Override - public boolean screenAvailable() { - return true; - } - - @Override - public synchronized Zoom getZoom() { - return zoom; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.LocalDataArea; +import com.jpexs.decompiler.flash.action.Stage; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.ecma.Undefined; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.gui.player.MediaDisplay; +import com.jpexs.decompiler.flash.gui.player.MediaDisplayListener; +import com.jpexs.decompiler.flash.gui.player.Zoom; +import com.jpexs.decompiler.flash.tags.DefineButtonSoundTag; +import com.jpexs.decompiler.flash.tags.DoActionTag; +import com.jpexs.decompiler.flash.tags.base.BoundedTag; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +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.tags.base.SoundTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.timeline.DepthState; +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.RECT; +import com.jpexs.decompiler.flash.types.SOUNDINFO; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Cache; +import com.jpexs.helpers.SerializableImage; +import com.jpexs.helpers.Stopwatch; +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Transparency; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionAdapter; +import java.awt.event.MouseMotionListener; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.VolatileImage; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.JLabel; +import javax.swing.JPanel; + +/** + * + * @author JPEXS + */ +public final class ImagePanel extends JPanel implements MediaDisplay { + + private static final Logger logger = Logger.getLogger(ImagePanel.class.getName()); + + private final List listeners = new ArrayList<>(); + + private Timelined timelined; + + private boolean stillFrame = false; + + private Timer timer; + + private int frame = -1; + + private boolean loop; + + private LocalDataArea lda; + + private boolean zoomAvailable = false; + + private SWF swf; + + private boolean loaded; + + private int mouseButton; + + private final JLabel debugLabel = new JLabel("-"); + + private Point cursorPosition = null; + + private MouseEvent lastMouseEvent = null; + + private final List soundPlayers = new ArrayList<>(); + + private final Cache displayObjectCache = Cache.getInstance(false, false, "displayObject"); + + private final IconPanel iconPanel; + + private int time = 0; + + private int selectedDepth = -1; + + private Zoom zoom = new Zoom(); + + private final Object delayObject = new Object(); + + private boolean drawReady; + + private final int drawWaitLimit = 50; // ms + + private TextTag textTag; + + private TextTag newTextTag; + + private int msPerFrame; + + private final boolean lowQuality = false; + + private final double LQ_FACTOR = 2; + + public synchronized void selectDepth(int depth) { + if (depth != selectedDepth) { + this.selectedDepth = depth; + } + + hideMouseSelection(); + } + + public void fireMediaDisplayStateChanged() { + for (MediaDisplayListener l : listeners) { + l.mediaDisplayStateChanged(this); + } + } + + @Override + public void addEventListener(MediaDisplayListener listener) { + listeners.add(listener); + } + + @Override + public void removeEventListener(MediaDisplayListener listener) { + listeners.remove(listener); + } + + private class IconPanel extends JPanel { + + private SerializableImage _img; + + private Rectangle _rect = null; + + private ButtonTag mouseOverButton = null; + + private boolean autoFit = false; + + private boolean allowMove = true; + + private Point dragStart = null; + + private Point offsetPoint = new Point(0, 0); + + private synchronized SerializableImage getImg() { + return _img; + } + + public synchronized Rectangle getRect() { + return _rect; + } + + public boolean hasAllowMove() { + return allowMove; + } + + VolatileImage renderImage; + + public void render() { + SerializableImage img = getImg(); + Rectangle rect = getRect(); + if (img == null) { + return; + } + if (renderImage == null) { + return; + } + Graphics2D g2 = null; + do { + + int valid = renderImage.validate(View.getDefaultConfiguration()); + + if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { + renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); + } + + try { + g2 = renderImage.createGraphics(); + g2.setPaint(View.transparentPaint); + g2.fill(new Rectangle(0, 0, getWidth(), getHeight())); + g2.setComposite(AlphaComposite.SrcOver); + g2.setPaint(View.getSwfBackgroundColor()); + g2.fill(new Rectangle(0, 0, getWidth(), getHeight())); + + g2.setComposite(AlphaComposite.SrcOver); + if (rect != null) { + g2.drawImage(img.getBufferedImage(), rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, 0, 0, img.getWidth(), img.getHeight(), null); + } + } finally { + if (g2 != null) { + g2.dispose(); + } + } + + } while (renderImage.contentsLost()); + } + + public IconPanel() { + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); + if (_img != null) { + calcRect(); + render(); + } + repaint(); + } + + }); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + dragStart = e.getPoint(); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + dragStart = null; + } + } + + }); + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + if (dragStart != null && allowMove) { + Point dragEnd = e.getPoint(); + Point delta = new Point(dragEnd.x - dragStart.x, dragEnd.y - dragStart.y); + offsetPoint.x += delta.x; + offsetPoint.y += delta.y; + dragStart = dragEnd; + repaint(); + } + } + + }); + } + + public void setAutoFit(boolean autoFit) { + this.autoFit = autoFit; + repaint(); + } + + public synchronized BufferedImage getLastImage() { + if (_img == null) { + return null; + } + return _img.getBufferedImage(); + } + + public synchronized void setImg(SerializableImage img) { + this._img = img; + if (img != null) { + calcRect(); + render(); + } + repaint(); + } + + public synchronized Point toImagePoint(Point p) { + if (_img == null) { + return null; + } + + return new Point((p.x - _rect.x) * _img.getWidth() / _rect.width, (p.y - _rect.y) * _img.getHeight() / _rect.height); + } + + private void setAllowMove(boolean allowMove) { + this.allowMove = allowMove; + if (!allowMove) { + offsetPoint = new Point(); + } + } + + private synchronized void calcRect() { + if (_img != null) { + int w1 = (int) (_img.getWidth() * (lowQuality ? LQ_FACTOR : 1)); + int h1 = (int) (_img.getHeight() * (lowQuality ? LQ_FACTOR : 1)); + + int w2 = getWidth(); + int h2 = getHeight(); + + int w; + int h; + if (autoFit) { + if (w1 <= w2 && h1 <= h2) { + w = w1; + h = h1; + } else { + + h = h1 * w2 / w1; + if (h > h2) { + w = w1 * h2 / h1; + h = h2; + } else { + w = w2; + } + } + } else { + w = w1; + h = h1; + } + + setAllowMove(h > h2 || w > w2); + _rect = new Rectangle(getWidth() / 2 - w / 2 + offsetPoint.x, getHeight() / 2 - h / 2 + offsetPoint.y, w, h); + } else { + _rect = null; + } + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + + if (renderImage != null) { + calcRect(); + if (renderImage.validate(View.getDefaultConfiguration()) != VolatileImage.IMAGE_OK) { + renderImage = View.createRenderImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); + render(); + } + + if (renderImage != null) { + g2d.drawImage(renderImage, 0, 0, null); + } + } + g2d.setColor(Color.red); + + DecimalFormat df = new DecimalFormat(); + df.setMaximumFractionDigits(2); + df.setMinimumFractionDigits(0); + df.setGroupingUsed(false); + + float frameLoss = 100 - (getFpsIs() / fpsShouldBe * 100); + + if (Configuration._debugMode.get()) { + g2d.drawString("frameLoss:" + df.format(frameLoss) + "%", 20, 20); + } + } + } + + @Override + public void setBackground(Color bg) { + if (iconPanel != null) { + iconPanel.setBackground(bg); + } + super.setBackground(bg); + } + + @Override + public synchronized void addMouseListener(MouseListener l) { + iconPanel.addMouseListener(l); + } + + @Override + public synchronized void removeMouseListener(MouseListener l) { + iconPanel.removeMouseListener(l); + } + + @Override + public synchronized void addMouseMotionListener(MouseMotionListener l) { + iconPanel.addMouseMotionListener(l); + } + + @Override + public synchronized void removeMouseMotionListener(MouseMotionListener l) { + iconPanel.removeMouseMotionListener(l); + } + + private void updatePos(Timelined timelined, MouseEvent lastMouseEvent, Timer thisTimer) { + if (timelined != null) { + + BoundedTag bounded = (BoundedTag) timelined; + RECT rect = bounded.getRect(); + int width = rect.getWidth(); + double scale = 1.0; + /*if (width > swf.displayRect.getWidth()) { + scale = (double) swf.displayRect.getWidth() / (double) width; + }*/ + Matrix m = Matrix.getTranslateInstance(-rect.Xmin, -rect.Ymin); + m.scale(scale); + + Point p = lastMouseEvent == null ? null : lastMouseEvent.getPoint(); + + synchronized (ImagePanel.class) { + if (timer == thisTimer) { + cursorPosition = p; + } + } + } + } + + private void showSelectedName() { + if (selectedDepth > -1 && frame > -1) { + DepthState ds = timelined.getTimeline().getFrame(frame).layers.get(selectedDepth); + if (ds != null) { + CharacterTag cht = timelined.getTimeline().swf.getCharacter(ds.characterId); + if (cht != null) { + debugLabel.setText(cht.getName()); + } + } + } + } + + public void hideMouseSelection() { + if (selectedDepth > -1) { + showSelectedName(); + } else { + debugLabel.setText(" - "); + } + } + + public ImagePanel() { + super(new BorderLayout()); + //iconPanel.setHorizontalAlignment(JLabel.CENTER); + setOpaque(true); + setBackground(View.getDefaultBackgroundColor()); + + loop = true; + iconPanel = new IconPanel(); + //labelPan.add(label, new GridBagConstraints()); + add(iconPanel, BorderLayout.CENTER); + add(debugLabel, BorderLayout.NORTH); + iconPanel.addMouseListener(new MouseAdapter() { + + @Override + public void mouseEntered(MouseEvent e) { + synchronized (ImagePanel.class) { + lastMouseEvent = e; + redraw(); + } + } + + @Override + public void mouseExited(MouseEvent e) { + synchronized (ImagePanel.class) { + lastMouseEvent = null; + hideMouseSelection(); + redraw(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + synchronized (ImagePanel.class) { + mouseButton = e.getButton(); + lastMouseEvent = e; + redraw(); + ButtonTag button = iconPanel.mouseOverButton; + if (button != null) { + DefineButtonSoundTag sounds = button.getSounds(); + if (sounds != null && sounds.buttonSoundChar2 != 0) { // OverUpToOverDown + playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar2), sounds.buttonSoundInfo2, timer); + } + } + } + } + + @Override + public void mouseReleased(MouseEvent e) { + synchronized (ImagePanel.class) { + mouseButton = 0; + lastMouseEvent = e; + redraw(); + ButtonTag button = iconPanel.mouseOverButton; + if (button != null) { + DefineButtonSoundTag sounds = button.getSounds(); + if (sounds != null && sounds.buttonSoundChar3 != 0) { // OverDownToOverUp + playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar3), sounds.buttonSoundInfo3, timer); + } + } + } + } + + }); + iconPanel.addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + synchronized (ImagePanel.class) { + lastMouseEvent = e; + redraw(); + } + } + + @Override + public void mouseDragged(MouseEvent e) { + synchronized (ImagePanel.class) { + lastMouseEvent = e; + redraw(); + } + } + + }); + } + + private synchronized void redraw() { + if (timer == null && timelined != null) { + startTimer(timelined.getTimeline(), false); + } + } + + @Override + public synchronized void zoom(Zoom zoom) { + boolean modified = this.zoom.value != zoom.value || this.zoom.fit != zoom.fit; + if (modified) { + this.zoom = zoom; + displayObjectCache.clear(); + redraw(); + if (textTag != null) { + setText(textTag, newTextTag); + } + + fireMediaDisplayStateChanged(); + } + } + + @Override + public synchronized BufferedImage printScreen() { + return iconPanel.getLastImage(); + } + + @Override + public synchronized double getZoomToFit() { + if (timelined != null) { + RECT bounds = timelined.getRect(); + double w1 = bounds.getWidth() / SWF.unitDivisor; + double h1 = bounds.getHeight() / SWF.unitDivisor; + + double w2 = getWidth(); + double h2 = getHeight(); + + double w; + double h; + h = h1 * w2 / w1; + if (h > h2) { + w = w1 * h2 / h1; + } else { + w = w2; + } + + if (w1 <= Double.MIN_NORMAL) { + return 1.0; + } + + return (double) w / (double) w1; + } + + return 1; + } + + @Override + public synchronized boolean zoomAvailable() { + return zoomAvailable; + } + + public void setTimelined(final Timelined drawable, final SWF swf, int frame) { + Stage stage = new Stage(drawable) { + @Override + public void callFrame(int frame) { + executeFrame(frame); + } + + @Override + public Object callFunction(long functionAddress, long functionLength, List args, Map regNames, Object thisObj) { + try { + SWFInputStream sis = new SWFInputStream(swf, swf.uncompressedData, functionAddress, (int) (functionAddress + functionLength)); + return execute(sis); + } catch (IOException ex) { + Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex); + } + return Undefined.INSTANCE; + } + + @Override + public int getCurrentFrame() { + return ImagePanel.this.getCurrentFrame(); + } + + @Override + public int getTotalFrames() { + return ImagePanel.this.getTotalFrames(); + } + + @Override + public void gotoFrame(int frame) { + ImagePanel.this.pause(); + ImagePanel.this.gotoFrame(frame); + } + + @Override + public void gotoLabel(String label) { + //TODO + } + + @Override + public void pause() { + ImagePanel.this.pause(); + } + + @Override + public void play() { + ImagePanel.this.play(); + } + + @Override + public void trace(Object... val) { + for (Object o : val) { + System.out.println("trace:" + o.toString()); + } + } + + }; + lda = new LocalDataArea(stage); + synchronized (ImagePanel.class) { + stopInternal(); + if (drawable instanceof ButtonTag) { + frame = ButtonTag.FRAME_UP; + } + + displayObjectCache.clear(); + this.timelined = drawable; + this.swf = swf; + zoomAvailable = true; + timer = null; + if (frame > -1) { + this.frame = frame; + this.stillFrame = true; + } else { + this.frame = 0; + this.stillFrame = false; + } + + loaded = true; + + if (drawable.getTimeline().getFrameCount() == 0) { + clearImagePanel(); + return; + } + + time = 0; + drawReady = false; + redraw(); + play(); + } + + synchronized (delayObject) { + try { + delayObject.wait(drawWaitLimit); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + synchronized (ImagePanel.class) { + if (!drawReady) { + clearImagePanel(); + } + } + + fireMediaDisplayStateChanged(); + } + + public synchronized void setImage(SerializableImage image) { + lda = null; + setBackground(View.getSwfBackgroundColor()); + clear(); + + timelined = null; + loaded = true; + stillFrame = true; + zoomAvailable = false; + iconPanel.setImg(image); + drawReady = true; + + fireMediaDisplayStateChanged(); + } + + public synchronized void setText(TextTag textTag, TextTag newTextTag) { + setBackground(View.getSwfBackgroundColor()); + clear(); + + lda = null; + timelined = null; + loaded = true; + stillFrame = true; + zoomAvailable = true; + + this.textTag = textTag; + this.newTextTag = newTextTag; + + double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; + + RECT rect = textTag.getRect(); + int width = (int) (rect.getWidth() * zoomDouble); + int height = (int) (rect.getHeight() * zoomDouble); + SerializableImage image = new SerializableImage((int) (width / SWF.unitDivisor) + 1, + (int) (height / SWF.unitDivisor) + 1, SerializableImage.TYPE_INT_ARGB); + image.fillTransparent(); + Matrix m = Matrix.getTranslateInstance(-rect.Xmin * zoomDouble, -rect.Ymin * zoomDouble); + m.scale(zoomDouble); + textTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFFC0C0C0)); + + if (newTextTag != null) { + newTextTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFF000000)); + } + + iconPanel.setImg(image); + drawReady = true; + + fireMediaDisplayStateChanged(); + } + + private synchronized void clearImagePanel() { + iconPanel.setImg(null); + } + + @Override + public synchronized int getCurrentFrame() { + return frame; + } + + @Override + public synchronized int getTotalFrames() { + if (timelined == null) { + return 0; + } + if (stillFrame) { + return 0; + } + return timelined.getTimeline().getFrameCount(); + } + + @Override + public void pause() { + stopInternal(); + redraw(); + } + + @Override + public void stop() { + stopInternal(); + rewind(); + redraw(); + } + + @Override + public void close() throws IOException { + stopInternal(); + } + + private void stopAllSounds() { + for (int i = soundPlayers.size() - 1; i >= 0; i--) { + SoundTagPlayer pl = soundPlayers.get(i); + pl.close(); + } + soundPlayers.clear(); + } + + private void clear() { + if (timer != null) { + timer.cancel(); + timer = null; + fireMediaDisplayStateChanged(); + } + + textTag = null; + newTextTag = null; + displayObjectCache.clear(); + } + + private void nextFrame(Timer thisTimer, final int cnt, final int timeShouldBe) { + drawFrame(thisTimer, true); + + synchronized (ImagePanel.class) { + if (timelined != null && timer == thisTimer) { + int frameCount = timelined.getTimeline().getFrameCount(); + + int oldFrame = frame; + for (int i = 0; i < cnt; i++) { + if (!stillFrame && frameCount > 0) { + frame = (frame + 1) % frameCount; + } + + if (!stillFrame && frame == frameCount - 1 && !loop) { + stopInternal(); + return; + } + + if (i < cnt - 1) { //skip not displayed frames, do not display, only play sounds, etc. + drawFrame(thisTimer, false); + } + } + if (frame != oldFrame) { + if (frame == 0) { + stopAllSounds(); + } + time = 0; + } else { + time = timeShouldBe; + } + } + } + + fireMediaDisplayStateChanged(); + } + + private static SerializableImage getFrame(SWF swf, int frame, int time, Timelined drawable, RenderContext renderContext, int selectedDepth, double zoom) { + Timeline timeline = drawable.getTimeline(); + //int mouseButton = renderContext.mouseButton; + //Point cursorPosition = renderContext.cursorPosition; + //String key = "drawable_" + frame + "_" + drawable.hashCode() + "_" + mouseButton + "_depth" + selectedDepth + "_" + (cursorPosition == null ? "out" : cursorPosition.hashCode()) + "_" + zoom + "_" + timeline.fontFrameNum; + SerializableImage img; + //SerializableImage img = swf.getFromCache(key); + //if (img == null) { + //boolean shouldCache = timeline.isSingleFrame(frame); + RECT rect = drawable.getRect(); + + int width = (int) (rect.getWidth() * zoom); + int height = (int) (rect.getHeight() * zoom); + SerializableImage image = new SerializableImage((int) Math.ceil(width / SWF.unitDivisor), + (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); + 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) { + ds = timeline.getFrame(frame).layers.get(selectedDepth); + } + + 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; + Shape outline = dt.getOutline(dframe, time, ds.ratio, renderContext, Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m.concatenate(new Matrix(ds.matrix))), true); + Rectangle bounds = outline.getBounds(); + gg.setStroke(new BasicStroke(2.0f, + BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, + 10.0f, new float[]{10.0f}, 0.0f)); + gg.setPaint(Color.red); + gg.draw(bounds); + } + } + } + + img = image; + + /*if (shouldCache) { + swf.putToCache(key, img); + }*/ + //} + return img; + } + + private Object execute(SWFInputStream sis) throws IOException { + if (!Configuration.internalFlashViewerExecuteAs12.get()) { + return Undefined.INSTANCE; + } + if (lda == null) { + return Undefined.INSTANCE; + } + long ip = sis.getPos(); + //System.err.println("============="); + Action a; + while ((a = sis.readAction()) != null) { + int actionLengthWithHeader = a.getTotalActionLength(); + a.setAddress(ip); + a.execute(lda); + /*System.err.print("" + a + ", stack: ["); + for (Object o : lda.stack) { + System.err.print("" + o + ","); + } + System.err.println("]");*/ + if (lda.returnValue != null) { + return lda.returnValue; + } + if (lda.jump != null) { + ip = lda.jump; + lda.jump = null; + } else { + ip += actionLengthWithHeader; + } + sis.seek(ip); + } + return Undefined.INSTANCE; + } + + private void executeFrame(int frame) { + if (!Configuration.internalFlashViewerExecuteAs12.get()) { + return; + } + if (timelined == null) { + return; + } + Frame f = timelined.getTimeline().getFrame(frame); + List actions = f.actions; + if (lda != null) { + lda.clear(); + } + for (DoActionTag src : actions) { + try { + ByteArrayRange actionBytes = src.getActionBytes(); + int prevLength = actionBytes.getPos(); + SWFInputStream rri = new SWFInputStream(swf, actionBytes.getArray(), 0, prevLength + actionBytes.getLength()); + if (prevLength != 0) { + rri.seek(prevLength); + } + execute(rri); + } catch (IOException ex) { + Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex); + } + + } + } + + private void drawFrame(Timer thisTimer, boolean display) { + Timelined timelined; + MouseEvent lastMouseEvent; + int frame; + int time; + Point cursorPosition; + int mouseButton; + int selectedDepth; + Zoom zoom; + SWF swf; + + synchronized (ImagePanel.class) { + timelined = this.timelined; + lastMouseEvent = this.lastMouseEvent; + } + + synchronized (ImagePanel.class) { + frame = this.frame; + time = this.time; + cursorPosition = this.cursorPosition; + if (cursorPosition != null) { + cursorPosition = iconPanel.toImagePoint(cursorPosition); + } + + mouseButton = this.mouseButton; + selectedDepth = this.selectedDepth; + zoom = this.zoom; + swf = this.swf; + } + + if (timelined == null) { + return; + } + + 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<>(); + + SerializableImage img; + try { + Timeline timeline = timelined.getTimeline(); + if (frame >= timeline.getFrameCount()) { + return; + } + + double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; + if (lowQuality) { + zoomDouble /= LQ_FACTOR; + } + updatePos(timelined, lastMouseEvent, thisTimer); + + Matrix mat = new Matrix(); + mat.translateX = swf.displayRect.Xmin; + mat.translateY = swf.displayRect.Ymin; + + img = null; + if (display) { + Stopwatch sw = Stopwatch.startNew(); + img = getFrame(swf, frame, time, timelined, renderContext, selectedDepth, zoomDouble); + sw.stop(); + if (sw.getElapsedMilliseconds() > 100) { + logger.log(Level.WARNING, "Slow rendering. {0}. frame, time={1}, {2}ms", new Object[]{frame, time, sw.getElapsedMilliseconds()}); + } + + if (renderContext.borderImage != null) { + img = renderContext.borderImage; + } + } + + List sounds = new ArrayList<>(); + List soundClasses = new ArrayList<>(); + timeline.getSounds(frame, time, renderContext.mouseOverButton, mouseButton, sounds, soundClasses); + for (int cid : swf.getCharacters().keySet()) { + CharacterTag c = swf.getCharacter(cid); + for (String cls : soundClasses) { + if (cls.equals(c.getClassName())) { + sounds.add(cid); + } + } + } + + for (int sndId : sounds) { + CharacterTag c = swf.getCharacter(sndId); + if (c instanceof SoundTag) { + SoundTag st = (SoundTag) c; + playSound(st, null, thisTimer); + } + } + executeFrame(frame); + } catch (Throwable ex) { + // swf was closed during the rendering probably + return; + } + if (display) { + + StringBuilder ret = new StringBuilder(); + + if (cursorPosition != null) { + ret.append(" [").append(cursorPosition.x).append(",").append(cursorPosition.y).append("] : "); + } + + boolean handCursor = renderContext.mouseOverButton != null; + boolean first = true; + for (int i = renderContext.stateUnderCursor.size() - 1; i >= 0; i--) { + DepthState ds = renderContext.stateUnderCursor.get(i); + if (!first) { + ret.append(", "); + } + + first = false; + CharacterTag c = swf.getCharacter(ds.characterId); + ret.append(c.toString()); + } + + if (first) { + ret.append(" - "); + } + + ButtonTag lastMouseOverButton; + synchronized (ImagePanel.class) { + if (timer == thisTimer) { + iconPanel.setImg(img); + 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 (lastMouseOverButton != renderContext.mouseOverButton) { + ButtonTag b = renderContext.mouseOverButton; + if (b != null) { + // New mouse entered + DefineButtonSoundTag sounds = b.getSounds(); + if (sounds != null && sounds.buttonSoundChar1 != 0) { // IdleToOverUp + playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar1), sounds.buttonSoundInfo1, timer); + } + } + + b = lastMouseOverButton; + if (b != null) { + // Old mouse leave + DefineButtonSoundTag sounds = b.getSounds(); + if (sounds != null && sounds.buttonSoundChar0 != 0) { // OverUpToIdle + playSound((SoundTag) swf.getCharacter(sounds.buttonSoundChar0), sounds.buttonSoundInfo0, timer); + } + } + } + + drawReady = true; + synchronized (delayObject) { + delayObject.notify(); + } + } + } + } + } + + private void playSound(SoundTag st, SOUNDINFO soundInfo, Timer thisTimer) { + final SoundTagPlayer sp; + try { + int loopCount = 1; + if (soundInfo != null && soundInfo.hasLoops) { + loopCount = Math.max(1, soundInfo.loopCount); + } + + sp = new SoundTagPlayer(st, loopCount, false); + sp.addEventListener(new MediaDisplayListener() { + + @Override + public void mediaDisplayStateChanged(MediaDisplay source) { + } + + @Override + public void playingFinished(MediaDisplay source) { + synchronized (ImagePanel.class) { + sp.close(); + soundPlayers.remove(sp); + } + } + }); + + synchronized (ImagePanel.class) { + if (timer != null && timer == thisTimer) { + soundPlayers.add(sp); + sp.play(); + } else { + sp.close(); + } + } + } catch (LineUnavailableException | IOException | UnsupportedAudioFileException ex) { + logger.log(Level.SEVERE, "Error during playing sound", ex); + } + } + + public synchronized void clearAll() { + stopInternal(); + clearImagePanel(); + timelined = null; + swf = null; + + fireMediaDisplayStateChanged(); + } + + private synchronized void stopInternal() { + clear(); + stopAllSounds(); + } + + @Override + public synchronized void play() { + stopInternal(); + if (timelined != null) { + Timeline timeline = timelined.getTimeline(); + if (!stillFrame && frame == timeline.getFrameCount() - 1) { + frame = 0; + } + + startTimer(timeline, true); + } + } + + private synchronized void setMsPerFrame(int val) { + this.msPerFrame = val; + } + + private synchronized int getMsPerFrame() { + return this.msPerFrame; + } + + private long startRun = 0L; + + private final long startDrop = 0L; + + private int skippedFrames = 0; + + private float fpsShouldBe = 0; + + private float fpsIs = 0; + + private Timer fpsTimer; + + private int startFrame = 0; + + private synchronized void setFpsIs(float val) { + fpsIs = val; + } + + private synchronized float getFpsIs() { + return fpsIs; + } + + private synchronized void setSkippedFrames(int val) { + skippedFrames = val; + } + + private synchronized void addSkippedFrames(int val) { + skippedFrames += val; + } + + private synchronized int getSkippedFrames() { + return skippedFrames; + } + + private synchronized int getAndResetSkippedFrames() { + int ret = skippedFrames; + skippedFrames = 0; + return ret; + } + + private void scheduleTask(boolean singleFrame, long msDelay) { + TimerTask task = new TimerTask() { + public final Timer thisTimer = timer; + + public final boolean isSingleFrame = singleFrame; + + private long lastRun = 0L; + + @Override + public void run() { + try { + synchronized (ImagePanel.class) { + if (timer != thisTimer) { + return; + } + } + lastRun = System.currentTimeMillis(); + int curFrame = frame; + long delay = getMsPerFrame(); + if (isSingleFrame) { + drawFrame(thisTimer, true); + synchronized (ImagePanel.class) { + thisTimer.cancel(); + if (timer == thisTimer) { + timer = null; + } + } + + fireMediaDisplayStateChanged(); + } else { + //Time before drawing current frame + long frameTimeMsIs = System.currentTimeMillis(); + //Total number of frames in this timeline + int frameCount = timelined.getTimeline().getFrameCount(); + //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 + int frameOverMaxShouldBeNow = startFrame + ticksFromStart; + //Apply maximum frames repating, this is actual frame which should be drawed now + int frameShouldBeNow = frameOverMaxShouldBeNow % frameCount; + + //How many frames are there between last displayed frame and now. For perfect display(=no framedrop), value should be 1 + int skipFrames = frameShouldBeNow - curFrame; + //It is negative for some reason, this will display older frames. Add frameCount to stay in modulu framecount. + if (skipFrames < 0) { + skipFrames += frameCount; + } + //Change for more than 1 frame + if (skipFrames > 1) { + addSkippedFrames(skipFrames - 1); //drop those frames, draw only last one + } + //Frame "time" - ticks in current frame + int currentFrameTicks = 0; + if (frameCount == 1) { //We have only one frame, so the ticks on that frame equal ticks on whole timeline + currentFrameTicks = ticksFromStart; + } + nextFrame(thisTimer, skipFrames, currentFrameTicks); + + long afterDrawFrameTimeMsIs = System.currentTimeMillis(); + + int nextFrameOverMax = frameOverMaxShouldBeNow; + while (delay < 0) { //while the frame time already passed + nextFrameOverMax++; + long nextFrameOverMaxTimeMsShouldBe = startRun + getMsPerFrame() * nextFrameOverMax; + delay = nextFrameOverMaxTimeMsShouldBe - afterDrawFrameTimeMsIs; + } + } + //schedule next run of the task + scheduleTask(isSingleFrame, delay); + + } catch (Exception ex) { + logger.log(Level.SEVERE, "Frame drawing error", ex); + } + } + }; + if (timer != null) { + timer.schedule(task, msDelay); + } + } + + private synchronized void startTimer(Timeline timeline, boolean playing) { + + startRun = System.currentTimeMillis(); + startFrame = frame; + float frameRate = timeline.frameRate; + setMsPerFrame(frameRate == 0 ? 1000 : (int) (1000.0 / frameRate)); + final boolean singleFrame = !playing + || (stillFrame && timeline.isSingleFrame(frame)) + || (!stillFrame && timeline.getRealFrameCount() <= 1 && timeline.isSingleFrame()); + + if (fpsTimer == null) { + fpsTimer = new Timer(); + fpsTimer.schedule(new TimerTask() { + @Override + public void run() { + float skipped = getAndResetSkippedFrames(); + setFpsIs(fpsShouldBe - skipped); + } + }, 1000, 1000); + } + timer = new Timer(); + fpsShouldBe = timeline.frameRate; + fpsIs = fpsShouldBe; + scheduleTask(singleFrame, 0); + + } + + @Override + public synchronized void rewind() { + frame = 0; + fireMediaDisplayStateChanged(); + } + + @Override + public synchronized boolean isPlaying() { + if (timelined == null || stillFrame) { + return false; + } + + return (timelined.getTimeline().getFrameCount() <= 1) || (timer != null); + } + + @Override + public void setLoop(boolean loop) { + this.loop = loop; + } + + @Override + public synchronized void gotoFrame(int frame) { + if (timelined == null) { + return; + } + Timeline timeline = timelined.getTimeline(); + if (frame >= timeline.getFrameCount()) { + return; + } + if (frame < 0) { + return; + } + + this.frame = frame; + stopInternal(); + redraw(); + fireMediaDisplayStateChanged(); + } + + @Override + public synchronized float getFrameRate() { + if (timelined == null) { + return 1; + } + if (stillFrame) { + return 1; + } + return timelined.getTimeline().frameRate; + } + + @Override + public synchronized boolean isLoaded() { + return loaded; + } + + @Override + public boolean loopAvailable() { + return false; + } + + @Override + public boolean screenAvailable() { + return true; + } + + @Override + public synchronized Zoom getZoom() { + return zoom; + } +}