выделение (бегущая строка, прокрутка текста) с заиканием java2d или javafx - PullRequest
1 голос
/ 25 мая 2020

Пытаюсь написать плавный бегунок (текст идет справа налево на экране). Это почти так, как я хочу, но все же есть некоторые заикания. Я бы хотел, чтобы он был таким же плавным, как движение облака по небу. 30 лет go Я справился с несколькими строками ассемблерного кода, но в Java у меня ничего не вышло.

Становится хуже, если я увеличиваю скорость (количество пикселей, на которое перемещаю текст за один раз).

Есть какая-то синхронизация с обновлением экрана sh отсутствует?

EDIT

Я обновил свой код в соответствии с примечанием @camickr к запустить окно в эксклюзивном полноэкранном режиме windows, что приведет к небольшому улучшению. Другие вещи, которые я пробовал:

  • Добавлен ExtendedBufferCapabilities, который должен учитывать vsyn c
  • Toolkit.getDefaultToolkit (). Syn c ();
  • включен opengl
  • пробовал играть l oop
  • добавил некоторую отладочную информацию

Когда я использую 30 кадров в секунду и перемещаю текст только на один пиксель на 4k Дисплей выглядит неплохо, но при этом очень медленный. Как только я увеличиваю скорость до 2 пикселей, он начинает заикаться.

Я начинаю думать, что достичь моей цели с помощью java2d просто невозможно, и мне нужно перейти на какую-нибудь opengl-библиотеку.

Вот мой код:

package scrolling;

import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.Timer;

import sun.java2d.pipe.hw.ExtendedBufferCapabilities;

/**
 * A smooth scroll with green background to be used with a video cutter.
 * 
 * sun.java2d.pipe.hw.ExtendedBufferCapabilities is restricted https://stackoverflow.com/questions/25222811/access-restriction-the-type-application-is-not-api-restriction-on-required-l
 * 
 */
public class MyScroll extends JFrame implements ActionListener {

    private int targetFps = 30;
    private boolean isOpenGl = false;
    private boolean isVsync = true;
    private boolean useGamingLoop = false;
    private int speed = 1;
    private String message;

    private int fontSize = 120;
    private Font theFont;
    private transient int leftEdge; // Offset from window's right edge to left edge
    private Color bgColor;
    private Color fgColor;
    private int winWidth;
    private int winHeight;
    private double position = 0.77;
    private FontMetrics fontMetrics;
    private int yPositionScroll;
    private boolean isFullScreen;
    private long lastTimerStart = 0;
    private BufferedImage img;
    private Graphics2D graphicsScroll;
    private GraphicsDevice currentScreenDevice = null;
    private int msgWidth = 0;
    private Timer scrollTimer;
    private boolean isRunning;

    /* gaming loop variables */
    private static final long NANO_IN_MILLI = 1000000L;
    // num of iterations with a sleep delay of 0ms before
    // game loop yields to other threads.
    private static final int NO_DELAYS_PER_YIELD = 16;
    // max num of renderings that can be skipped in one game loop,
    // game's internal state is updated but not rendered on screen.
    private static int MAX_RENDER_SKIPS = 5;
    // private long prevStatsTime;
    private long gameStartTime;
    private long curRenderTime;
    private long rendersSkipped = 0L;
    private long period; // period between rendering in nanosecs
    private long fps;
    private long frameCounter;
    private long lastFpsTime;

    public void init() {
        fontSize = getWidth() / 17;

        if (getGraphicsConfiguration().getBufferCapabilities().isPageFlipping()) {    
            try { // no pageflipping available with opengl
                BufferCapabilities cap = new BufferCapabilities(new ImageCapabilities(true), new ImageCapabilities(true), BufferCapabilities.FlipContents.BACKGROUND);
                // ExtendedBufferCapabilities is supposed to do a vsync
                ExtendedBufferCapabilities ebc = new ExtendedBufferCapabilities(cap, ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
                createBufferStrategy(2, ebc);
            } catch (AWTException e) {
                e.printStackTrace();
            }

        } else {
            createBufferStrategy(2);
        }

        System.out.println(getDeviceConfigurationString(getGraphicsConfiguration()));

        message = "This is a test.   ";
        leftEdge = 0;
        theFont = new Font("Helvetica", Font.PLAIN, fontSize);
        bgColor = getBackground();
        fgColor = getForeground();
        winWidth = getSize().width - 1;
        winHeight = getSize().height;
        yPositionScroll = (int) (winHeight * position);

        initScrollImage();
    }

    /**
     * Draw the entire text to a buffered image to copy it to the screen later.
     */
    private void initScrollImage() {
        Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
        fontMetrics = og.getFontMetrics(theFont);
        Rectangle2D rect = fontMetrics.getStringBounds(message, og);
        img = new BufferedImage((int) rect.getWidth(), (int) rect.getHeight(), BufferedImage.TYPE_INT_ARGB);

        // At each frame, we get a reference on the rendering buffer graphics2d.
        // To handle concurrency, we 'cut' it into graphics context for each cube.
        graphicsScroll = img.createGraphics();
        graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphicsScroll.setBackground(Color.BLACK);

        graphicsScroll.setFont(theFont);
        graphicsScroll.setColor(bgColor);
        graphicsScroll.fillRect(0, 0, img.getWidth(), img.getHeight()); // clear offScreen Image.
        graphicsScroll.setColor(fgColor);
        msgWidth = fontMetrics.stringWidth(message);

        graphicsScroll.setColor(Color.white);
        graphicsScroll.drawString(message, 1, img.getHeight() - 10);

        // for better readability in front of an image draw an outline arround the text
        graphicsScroll.setColor(Color.black);
        graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        Font font = new Font("Helvetica", Font.PLAIN, fontSize);
        graphicsScroll.translate(1, img.getHeight() - 10);
        FontRenderContext frc = graphicsScroll.getFontRenderContext();
        GlyphVector gv = font.createGlyphVector(frc, message);
        graphicsScroll.draw(gv.getOutline());
    }

    public void start() {
        scrollTimer = new Timer(1000 / targetFps, this);
        scrollTimer.setRepeats(true);
        scrollTimer.setCoalesce(true);
        scrollTimer.start();        
    }

    public void startGamingloop() {
        // loop initialization
        long beforeTime, afterTime, timeDiff, sleepTime;
        long overSleepTime = 0L;
        int noDelays = 0;
        long excess = 0L;
        gameStartTime = System.nanoTime();
        // prevStatsTime = gameStartTime;
        beforeTime = gameStartTime;

        period = (1000L * NANO_IN_MILLI) / targetFps; // rendering FPS (nanosecs/targetFPS)
        System.out.println("FPS: " + targetFps + ", vsync=");
        System.out.println("FPS period: " + period);

        // gaming loop http://www.javagaming.org/index.php/topic,19971.0.html
        while (true) {
            // **2) execute physics
            updateLeftEdge();

            // **1) execute drawing
            drawScroll();

            // Synchronise with the display hardware.
            // Flip the buffer
            if (!getBufferStrategy().contentsLost()) {
                getBufferStrategy().show();
            }
            if (isVsync) {
                Toolkit.getDefaultToolkit().sync();
            }

            afterTime = System.nanoTime();
            curRenderTime = afterTime;
            calculateFramesPerSecond();

            timeDiff = afterTime - beforeTime;
            sleepTime = (period - timeDiff) - overSleepTime;
            if (sleepTime > 0) { // time left in cycle
                // System.out.println("sleepTime: " + (sleepTime/NANO_IN_MILLI));
                try {
                    Thread.sleep(sleepTime / NANO_IN_MILLI);// nano->ms
                } catch (InterruptedException ex) {
                }
                overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
            } else { // sleepTime <= 0;
                System.out.println("Rendering too slow");
                // this cycle took longer than period
                excess -= sleepTime;
                // store excess time value
                overSleepTime = 0L;
                if (++noDelays >= NO_DELAYS_PER_YIELD) {
                    Thread.yield();
                    // give another thread a chance to run
                    noDelays = 0;
                }
            }

            beforeTime = System.nanoTime();

            /*
             * If the rendering is taking too long, then update the game state without rendering it, to get the UPS nearer to the required frame rate.
             */
            int skips = 0;
            while ((excess > period) && (skips < MAX_RENDER_SKIPS)) {
                // update state but don’t render
                System.out.println("Skip renderFPS, run updateFPS");
                excess -= period;
                updateLeftEdge();
                skips++;
            }
            rendersSkipped += skips;
        }
    }

    private void calculateFramesPerSecond() {
        if (curRenderTime - lastFpsTime >= NANO_IN_MILLI * 1000) {
            fps = frameCounter;
            frameCounter = 0;
            lastFpsTime = curRenderTime;
        }
        frameCounter++;
    }

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        render();
    }

    private void render() {
        if (!isFullScreen) {
            repaint(0, yPositionScroll, winWidth, yPositionScroll + fontMetrics.getAscent());
        } else {
            getBufferStrategy().show();
        }
        if (isVsync) {
            Toolkit.getDefaultToolkit().sync();
        }
        updateLeftEdge();
        drawScroll();
    }

    /**
     * Draws (part) of the prerendered image with text to a position in the screen defined by an increasing 
     * variable "leftEdge".
     * 
     * @return time drawing took. 
     */
    private long drawScroll() {
        long beforeDrawText = System.nanoTime();

        if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
            leftEdge = 0;
        }
        int x = winWidth - leftEdge;
        int sourceWidth = Math.min(leftEdge, img.getWidth());
        Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
        try { // copy the pre drawn scroll to the screen
            og.drawImage(img.getSubimage(0, 0, sourceWidth, img.getHeight()), x, yPositionScroll, null);
        } catch (Exception e) {
            System.out.println(e.getMessage() + " " + x + " " + sourceWidth);
        }
        long afterDrawText = System.nanoTime();
        return afterDrawText - beforeDrawText;
    }

    public static void main(String[] args) {
        MyScroll scroll = new MyScroll();

        System.setProperty("sun.java2d.opengl", String.valueOf(scroll.isOpenGl())); // enable opengl
        System.setProperty("sun.java2d.renderer.verbose", "true");
        String renderer = "undefined";
        try {
            renderer = sun.java2d.pipe.RenderingEngine.getInstance().getClass().getName();
            System.out.println("Renderer " + renderer);
        } catch (Throwable th) {
            // may fail with JDK9 jigsaw (jake)
            if (false) {
                System.err.println("Unable to get RenderingEngine.getInstance()");
                th.printStackTrace();
            }
        }

        scroll.setBackground(Color.green);
        scroll.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] screens = env.getScreenDevices();
        // I want the external monitor attached to my notebook
        GraphicsDevice device = screens[screens.length - 1]; 
        boolean isFullScreenSupported = device.isFullScreenSupported();

        scroll.setFullScreen(isFullScreenSupported);
        scroll.setUndecorated(isFullScreenSupported);
        scroll.setResizable(!isFullScreenSupported);

        if (isFullScreenSupported) {
            device.setFullScreenWindow(scroll);
            scroll.setIgnoreRepaint(true);
            scroll.validate();
        } else {
            // Windowed mode
            Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
            scroll.setSize(r.width, r.height);
            scroll.pack();
            scroll.setExtendedState(JFrame.MAXIMIZED_BOTH);
            scroll.setVisible(true);
        }

        // exit on pressing escape
        scroll.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    scroll.setRunning(false);
                    if(scroll.getScrollTimer() != null) {
                        scroll.getScrollTimer().stop();
                    }
                    System.exit(0);
                }
            }
        });

        scroll.setVisible(true);
        scroll.init();
        if (scroll.isUseGamingLoop()) {
            scroll.startGamingloop();
        } else {
            scroll.start();
        }

    }

    private void updateLeftEdge() {
        leftEdge += speed;
    }

    public Timer getScrollTimer() {
        return scrollTimer;
    }

    public void setFullScreen(boolean isFullScreen) {
        this.isFullScreen = isFullScreen;
    }

    public void setTargetFps(int targetFps) {
        this.targetFps = targetFps;
    }

    public void setOpenGl(boolean isOpenGl) {
        this.isOpenGl = isOpenGl;
    }

    public void setVsync(boolean isVsync) {
        this.isVsync = isVsync;
    }

    public void setUseGamingLoop(boolean useGamingLoop) {
        this.useGamingLoop = useGamingLoop;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    private String getDeviceConfigurationString(GraphicsConfiguration gc){
        return "Bounds: " + gc.getBounds() + "\n" + 
                "Buffer Capabilities: " + gc.getBufferCapabilities() + "\n" +
                "   Back Buffer Capabilities: " + gc.getBufferCapabilities().getBackBufferCapabilities() + "\n" +
                "      Accelerated: " + gc.getBufferCapabilities().getBackBufferCapabilities().isAccelerated() + "\n" + 
                "      True Volatile: " + gc.getBufferCapabilities().getBackBufferCapabilities().isTrueVolatile() + "\n" +
                "   Flip Contents: " + gc.getBufferCapabilities().getFlipContents() + "\n" +
                "   Front Buffer Capabilities: " + gc.getBufferCapabilities().getFrontBufferCapabilities() + "\n" +
                "      Accelerated: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isAccelerated() + "\n" +
                "      True Volatile: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isTrueVolatile() + "\n" +
                "   Is Full Screen Required: " + gc.getBufferCapabilities().isFullScreenRequired() + "\n" +
                "   Is MultiBuffer Available: " + gc.getBufferCapabilities().isMultiBufferAvailable() + "\n" +
                "   Is Page Flipping: " + gc.getBufferCapabilities().isPageFlipping() + "\n" +
                "Device: " + gc.getDevice() + "\n" +
                "   Available Accelerated Memory: " + gc.getDevice().getAvailableAcceleratedMemory() + "\n" +
                "   ID String: " + gc.getDevice().getIDstring() + "\n" +
                "   Type: " + gc.getDevice().getType() + "\n" +
                "   Display Mode: " + gc.getDevice().getDisplayMode() + "\n" +              
                "Image Capabilities: " + gc.getImageCapabilities() + "\n" + 
                "      Accelerated: " + gc.getImageCapabilities().isAccelerated() + "\n" + 
                "      True Volatile: " + gc.getImageCapabilities().isTrueVolatile() + "\n";        
    }

    public boolean isOpenGl() {
        return isOpenGl;
    }

    public boolean isUseGamingLoop() {
        return useGamingLoop;
    }    
}

Вывод графических возможностей:

Renderer sun.java2d.pisces.PiscesRenderingEngine
Bounds: java.awt.Rectangle[x=3839,y=0,width=3840,height=2160]
Buffer Capabilities: sun.awt.X11GraphicsConfig$XDBECapabilities@68de145
   Back Buffer Capabilities: java.awt.ImageCapabilities@27fa135a
      Accelerated: false
      True Volatile: false
   Flip Contents: undefined
   Front Buffer Capabilities: java.awt.ImageCapabilities@27fa135a
      Accelerated: false
      True Volatile: false
   Is Full Screen Required: false
   Is MultiBuffer Available: false
   Is Page Flipping: true
Device: X11GraphicsDevice[screen=1]
   Available Accelerated Memory: -1
   ID String: :0.1
   Type: 0
   Display Mode: java.awt.DisplayMode@1769
Image Capabilities: java.awt.ImageCapabilities@27fa135a
      Accelerated: false
      True Volatile: false

EDIT 2: Я написал то же самое в javafx сейчас , результат тот же, он заикается, как только я перемещаю более 3 пикселей одновременно.

Я использую Intel i9 9900K, Nvidia GeForce RTX 2060 Mobile, Ubuntu, OpenJdk 14.

    package scrolling;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import javax.swing.Timer;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Screen;
import javafx.stage.Stage;

/**
 * A smooth scroll with green background to be used with a video cutter.
 * 
 * https://stackoverflow.com/questions/51478675/error-javafx-runtime-components-are-missing-and-are-required-to-run-this-appli
 * https://stackoverflow.com/questions/18547362/javafx-and-openjdk
 * 
 */
public class MyScroll extends Application {

    private boolean useGamingLoop = false;
    private int speed = 3;
    private String message;

    private transient double leftEdge; // Offset from window's right edge to left edge
    private Color bgColor;
    private Color fgColor;
    private double winWidth;
    private int winHeight;
    private double position = 0.77;
    private int yPositionScroll;
    private Image img;
    private int msgWidth = 0;
    private Timer scrollTimer;
    private boolean isRunning;
    private ImageView imageView;
    long lastUpdateTime;
    long lastIntervall;
    long nextIntervall;
    String ADAPTIVE_PULSE_PROP = "com.sun.scenario.animation.adaptivepulse";

    int frame = 0;
    long timeOfLastFrameSwitch = 0; 
    @Override
    public void start(final Stage stage) {
        message = "This is a test.   ";
        leftEdge = 0;
        bgColor = Color.green;
        fgColor = Color.white;
        winWidth = (int)Screen.getPrimary().getBounds().getWidth();
        winHeight = (int)Screen.getPrimary().getBounds().getHeight();
        yPositionScroll = (int) (winHeight * position);

        initScrollImage(stage);

        stage.setFullScreenExitHint("");
        stage.setAlwaysOnTop(true);


        new AnimationTimer() {
            @Override
            public void handle(long now) {
                 nextIntervall = now - lastUpdateTime;
                 System.out.println(lastIntervall - nextIntervall);
                 lastUpdateTime = System.nanoTime();
                 drawScroll(stage);
                 lastIntervall = nextIntervall;
            }
        }.start();

        //Creating a Group object  
        Group root = new Group(imageView);  
        //Creating a scene object 
        Scene scene = new Scene(root);  
        //Adding scene to the stage 
        stage.setScene(scene);
        stage.show();
        stage.setFullScreen(true);        
    }


    /**
     * Draw the entire text to an imageview and add to the scene.
     */
    private void initScrollImage(Stage stage) {
        int fontSize = (int)winWidth / 17;

        Font theFont = new Font("Helvetica", Font.PLAIN, fontSize);
        BufferedImage tempImg = new BufferedImage((int)winWidth, winHeight, BufferedImage.TYPE_INT_ARGB);
        FontMetrics fontMetrics = tempImg.getGraphics().getFontMetrics(theFont);
        Rectangle2D rect = fontMetrics.getStringBounds(message, tempImg.getGraphics());
        msgWidth = fontMetrics.stringWidth(message);

        BufferedImage bufferedImage = new BufferedImage((int) rect.getWidth(), (int) rect.getHeight(), BufferedImage.TYPE_INT_ARGB);

        Graphics2D graphicsScroll = bufferedImage.createGraphics();
        graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphicsScroll.setBackground(Color.BLACK);

        graphicsScroll.setFont(theFont);
        graphicsScroll.setColor(bgColor);
        graphicsScroll.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); // set background color
        graphicsScroll.setColor(fgColor);
        graphicsScroll.drawString(message, 1, bufferedImage.getHeight() - 10);

        // for better readability in front of an image draw an outline arround the text
        graphicsScroll.setColor(Color.black);
        graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        Font font = new Font("Helvetica", Font.PLAIN, fontSize);
        graphicsScroll.translate(1, bufferedImage.getHeight() - 10);
        FontRenderContext frc = graphicsScroll.getFontRenderContext();
        GlyphVector gv = font.createGlyphVector(frc, message);
        graphicsScroll.draw(gv.getOutline());

        img = SwingFXUtils.toFXImage(bufferedImage, null);

        imageView = new ImageView(img); 
        imageView.setSmooth(false);
        imageView.setCache(true);

        //Setting the preserve ratio of the image view 
        imageView.setPreserveRatio(true);  

        tempImg.flush();
    }


    /**
     * Draws (part) of the prerendered image with text to a position in the screen defined by an increasing 
     * variable "leftEdge".
     * 
     * @return time drawing took. 
     */
    private void drawScroll(Stage stage) {
        leftEdge += speed;
        if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
            leftEdge = 0;
        }
//        imageView.relocate(winWidth - leftEdge, yPositionScroll);
        imageView.setX(winWidth - leftEdge);
    }

    public static void main(String[] args) {
//      System.setProperty("sun.java2d.opengl", "true");
        System.setProperty("prism.vsync", "true");
//      System.setProperty("com.sun.scenario.animation.adaptivepulse", "true");
        System.setProperty("com.sun.scenario.animation.vsync", "true");

        launch(args);
    }

    public Timer getScrollTimer() {
        return scrollTimer;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public boolean isUseGamingLoop() {
        return useGamingLoop;
    }


    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }    
}

Ответы [ 2 ]

1 голос
/ 28 мая 2020

Я использую это с Windows 10. Я не думаю, что он будет хорошо работать в системе Unix.

Marquee GUI 1

...

Marquee GUI 2

Код состоит из 9 классов в 5 пакетах. Имя пакета указано в коде.

Marquee Class

package com.ggl.marquee;

import javax.swing.SwingUtilities;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;

public class Marquee implements Runnable {

    @Override
    public void run() {
        new MarqueeFrame(new MarqueeModel());
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Marquee());
    }

}

CreateMarqueeActionListener Class

package com.ggl.marquee.controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JTextField;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;

public class CreateMarqueeActionListener implements ActionListener {

    private JTextField field;

    private MarqueeFrame frame;

    private MarqueeModel model;

    public CreateMarqueeActionListener(MarqueeFrame frame, MarqueeModel model,
            JTextField field) {
        this.frame = frame;
        this.model = model;
        this.field = field;
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        model.stopDtpRunnable();
        model.resetPixels();

        String s = field.getText().trim();
        if (s.equals("")) {
            frame.repaintMarqueePanel();
            return;
        }

        s = " " + s + "    ";
        model.setTextPixels(model.getDefaultFont().getTextPixels(s));
        frame.repaintMarqueePanel();
    }

}

FontSelectionListener Class

package com.ggl.marquee.controller;

import javax.swing.DefaultListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;

public class FontSelectionListener implements ListSelectionListener {

    private MarqueeModel model;

    public FontSelectionListener(MarqueeModel model) {
        this.model = model;
    }

    @Override
    public void valueChanged(ListSelectionEvent event) {
        DefaultListSelectionModel selectionModel = (DefaultListSelectionModel) event
                .getSource();
        if (!event.getValueIsAdjusting()) {
            int index = selectionModel.getMinSelectionIndex();
            if (index >= 0) {
                MarqueeFont font = model.getDefaultListModel().get(index);
                model.setDefaultFont(font);
            }
        }
    }

}

FontGenerator Class

package com.ggl.marquee.model;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FontGenerator {

    private static final boolean DEBUG = false;

    private Font font;

    private FontHeights fontHeights;

    private Map<Character, MarqueeCharacter> characterMap;

    public FontGenerator(Font font) {
        this.font = font;
        this.characterMap = new HashMap<Character, MarqueeCharacter>();
    }

    public void execute() {
        int width = 50;
        BufferedImage bi = generateCharacterImage(width, "B");
        int[] result1 = getCharacterHeight(bi);
        bi = generateCharacterImage(width, "g");
        int[] result2 = getCharacterHeight(bi);
        fontHeights = new FontHeights(result1[0], result1[1], result2[1]);

        if (DEBUG) System.out.println(fontHeights.getAscender() + ", "
                + fontHeights.getBaseline() + ", "
                + fontHeights.getDescender());

        for (int x = 32; x < 127; x++) {
            char c = (char) x;

            StringBuilder builder = new StringBuilder(3);
            builder.append('H');
            builder.append(c);
            builder.append('H');

            bi = generateCharacterImage(width, builder.toString());
            int[][] pixels = convertTo2D(bi);
            MarqueeCharacter mc = getCharacterPixels(pixels);

            if (DEBUG) {
                System.out.println(builder.toString() + " " +
                        mc.getWidth() + "x" + mc.getHeight());
            }

            characterMap.put(c, mc);
        }

    }

    private BufferedImage generateCharacterImage(int width, String string) {
        BufferedImage bi = new BufferedImage(
                width, width, BufferedImage.TYPE_INT_RGB);
        Graphics g = bi.getGraphics();
        g.setFont(font);

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, width);

        g.setColor(Color.BLACK);
        g.drawString(string, 0, width / 2);

        return bi;
    }

    private int[] getCharacterHeight(BufferedImage bi) {
        int[][] pixels = convertTo2D(bi);
        int minHeight = bi.getHeight();
        int maxHeight = 0;

        for (int i = 0; i < pixels.length; i++) {
            for (int j = 0; j < pixels[i].length; j++) {
                if (pixels[i][j] < -1) {
                    minHeight = Math.min(i, minHeight);
                    maxHeight = Math.max(i, maxHeight);
                }
            }
        }

        int[] result = new int[2];
        result[0] = minHeight;
        result[1] = maxHeight;
        return result;
    }

    private MarqueeCharacter getCharacterPixels(int[][] pixels) {
        List<Boolean[]> list = new ArrayList<Boolean[]>();
        int startRow = fontHeights.getAscender();
        int endRow = fontHeights.getDescender();
        int height = fontHeights.getCharacterHeight();
        int startColumn = getCharacterColumnStart(pixels);
        int endColumn = getCharacterColumnEnd(pixels);

        for (int i = startColumn; i <= endColumn; i++) {
            Boolean[] characterColumn = new Boolean[height];
            int k = 0;
            for (int j = startRow; j <= endRow; j++) {
                if (pixels[j][i] < -1) characterColumn[k] = true;
                else characterColumn[k] = false;
                k++;
            }
            list.add(characterColumn);
        }

        MarqueeCharacter mc = new MarqueeCharacter(list.size(), height);

        for (int i = 0; i < list.size(); i++) {
            Boolean[] characterColumn = list.get(i);
            mc.setColumn(characterColumn);
        }

        return mc;
    }

    private int getCharacterColumnStart(int[][] pixels) {
        int start = fontHeights.getAscender();
        int end = fontHeights.getBaseline();
        int letterEndFlag = 0;
        int column = 1;

        while (letterEndFlag < 1) {
            boolean pixelDetected = false;
            for (int i = start; i <= end; i++) {
                if (pixels[i][column] < -1) {
                    pixelDetected = true;
                }
            }

            column++;

            // End of first letter
            if ((letterEndFlag == 0) && !pixelDetected) letterEndFlag = 1;
        }

        return column;
    }

    private int getCharacterColumnEnd(int[][] pixels) {
        int start = fontHeights.getAscender();
        int end = fontHeights.getBaseline();
        int height = fontHeights.getCharacterHeight2();
        int letterEndFlag = 0;
        int column = pixels.length - 1;

        while (letterEndFlag < 4) {
            int pixelCount = 0;
            for (int i = start; i <= end; i++) {
                if (pixels[i][column] < -1) {
                    pixelCount++;
                }
            }

            column--;

            // End of first letter
            if (pixelCount >= height) letterEndFlag++;
            // Start of first letter
//          if ((letterEndFlag == 0) && (pixelCount > 0)) letterEndFlag = 1;
        }

        return column;
    }

    private int[][] convertTo2D(BufferedImage image) {
        final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
                .getData();
        final int width = image.getWidth();
        final int height = image.getHeight();

        int[][] result = new int[height][width];

        for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel++) {
            result[row][col] = pixels[pixel];
            col++;
            if (col == width) {
                col = 0;
                row++;
            }
        }

        return result;
    }

    public MarqueeCharacter getCharacter(Character c) {
        MarqueeCharacter mc = characterMap.get(c);
        return (mc == null) ? characterMap.get('?') : mc;
    }

    public int getCharacterHeight() {
        return fontHeights.getCharacterHeight();
    }
}

Класс FontHeights

package com.ggl.marquee.model;

public class FontHeights {

    private final int ascender;
    private final int baseline;
    private final int descender;

    public FontHeights(int ascender, int baseline, int descender) {
        this.ascender = ascender;
        this.baseline = baseline;
        this.descender = descender;
    }

    public int getCharacterHeight() {
        return descender - ascender + 1;
    }

    public int getCharacterHeight2() {
        return baseline - ascender + 1;
    }

    public int getAscender() {
        return ascender;
    }

    public int getBaseline() {
        return baseline;
    }

    public int getDescender() {
        return descender;
    }

}

Класс MarqueeCharacter

package com.ggl.marquee.model;

import java.security.InvalidParameterException;

public class MarqueeCharacter {

    private static int columnCount;

    private int height;
    private int width;

    private boolean[][] pixels;

    public MarqueeCharacter(int width, int height) {
        this.width = width;
        this.height = height;
        this.pixels = new boolean[width][height];
        columnCount = 0;
    }

    public void setColumn(Boolean[] value) {
        int height = value.length;
        if (this.height != height) {
            String s = "The number of values must equal the column height - "
                    + this.height;
            throw new InvalidParameterException(s);
        }

        for (int i = 0; i < height; i++) {
            pixels[columnCount][i] = value[i];
        }

        columnCount++;
    }

    public boolean[][] getPixels() {
        return pixels;
    }

    public boolean isComplete() {
        return (width == columnCount);
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

}

Класс MarqueeFont

package com.ggl.marquee.model;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

public class MarqueeFont {

    private static final boolean DEBUG = false;

    private int fontHeight;

    private Font font;

    public MarqueeFont(Font font) {
        this.font = font;

        FontRenderContext frc = new FontRenderContext(null, true, true);
        Rectangle2D r2D = font.getStringBounds("HgH", frc);
        this.fontHeight = (int) Math.round(r2D.getHeight());

        if (DEBUG) {
            System.out.println(font.getFamily() + " " + fontHeight + " pixels");
        }
    }

    public boolean[][] getTextPixels(String s) {
        FontRenderContext frc = new FontRenderContext(null, true, true);

        Rectangle2D r2D = font.getStringBounds(s, frc);
        int rWidth = (int) Math.round(r2D.getWidth());
        int rHeight = (int) Math.round(r2D.getHeight());
        int rX = (int) Math.round(r2D.getX());
        int rY = (int) Math.round(r2D.getY());

        if (DEBUG) {
            System.out.print(s);
            System.out.print(", rWidth = " + rWidth);
            System.out.print(", rHeight = " + rHeight);
            System.out.println(", rX = " + rX + ", rY = " + rY);
        }

        BufferedImage bi = generateCharacterImage(rX, -rY, rWidth, rHeight, s);
        int[][] pixels = convertTo2D(bi);

        if (DEBUG) {
            displayPixels(pixels);
        }

        return createTextPixels(pixels);
    }

    private BufferedImage generateCharacterImage(int x, int y, int width,
            int height, String string) {
        BufferedImage bi = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics g = bi.getGraphics();
        g.setFont(font);

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);

        g.setColor(Color.BLACK);
        g.drawString(string, x, y);

        return bi;
    }

    private int[][] convertTo2D(BufferedImage image) {
        final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
                .getData();
        final int width = image.getWidth();
        final int height = image.getHeight();

        int[][] result = new int[height][width];

        int row = 0;
        int col = 0;
        for (int pixel = 0; pixel < pixels.length; pixel++) {
            result[row][col] = pixels[pixel];
            col++;
            if (col == width) {
                col = 0;
                row++;
            }
        }

        return result;
    }

    private void displayPixels(int[][] pixels) {
        for (int i = 0; i < pixels.length; i++) {
            String s = String.format("%03d", (i + 1));
            System.out.print(s + ". ");
            for (int j = 0; j < pixels[i].length; j++) {
                if (pixels[i][j] == -1) {
                    System.out.print("  ");
                } else {
                    System.out.print("X ");
                }
            }
            System.out.println("");
        }
    }

    private boolean[][] createTextPixels(int[][] pixels) {
        // The int array pixels is in column, row order.
        // We have to flip the array and produce the output
        // in row, column order.
        if (DEBUG) {
            System.out.println(pixels[0].length + "x" + pixels.length);
        }
        boolean[][] textPixels = new boolean[pixels[0].length][pixels.length];
        for (int i = 0; i < pixels.length; i++) {
            for (int j = 0; j < pixels[i].length; j++) {
                if (pixels[i][j] == -1) {
                    textPixels[j][i] = false;
                } else {
                    textPixels[j][i] = true;
                }
            }
        }

        return textPixels;
    }

    public Font getFont() {
        return font;
    }

    public int getFontHeight() {
        return fontHeight;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(font.getFamily());
        builder.append(", ");
        builder.append(getStyleText());
        builder.append(", ");
        builder.append(font.getSize());
        builder.append(" pixels");

        return builder.toString();
    }

    private StringBuilder getStyleText() {
        StringBuilder builder = new StringBuilder();
        int style = font.getStyle();
        if (style == Font.PLAIN) {
            builder.append("normal");
        } else if (style == Font.BOLD) {
            builder.append("bold");
        } else if (style == Font.ITALIC) {
            builder.append("italic");
        } else if (style == (Font.BOLD + Font.ITALIC)) {
            builder.append("bold italic");
        } else {
            builder.append("unknown style");
        }
        return builder;
    }

}

Класс MarqueeFontFactory

package com.ggl.marquee.model;

import java.awt.Font;

import javax.swing.DefaultListModel;

public class MarqueeFontFactory {

    private DefaultListModel<MarqueeFont> fontList;

    private MarqueeFont defaultFont;

    public MarqueeFontFactory() {
        this.fontList = new DefaultListModel<MarqueeFont>();
        addElements();
    }

    private void addElements() {
        this.defaultFont = new MarqueeFont(new Font("Arial", Font.BOLD, 16));
        fontList.addElement(defaultFont);
        fontList.addElement(new MarqueeFont(new Font("Cambria", Font.BOLD, 16)));
        fontList.addElement(new MarqueeFont(new Font("Courier New", Font.BOLD,
                16)));
        fontList.addElement(new MarqueeFont(new Font("Georgia", Font.BOLD, 16)));
        fontList.addElement(new MarqueeFont(new Font("Lucida Calligraphy",
                Font.BOLD, 16)));
        fontList.addElement(new MarqueeFont(new Font("Times New Roman",
                Font.BOLD, 16)));
        fontList.addElement(new MarqueeFont(new Font("Verdana", Font.BOLD, 16)));
    }

    public DefaultListModel<MarqueeFont> getFontList() {
        return fontList;
    }

    public void setDefaultFont(MarqueeFont defaultFont) {
        this.defaultFont = defaultFont;
    }

    public MarqueeFont getDefaultFont() {
        return defaultFont;
    }

    public int getCharacterHeight() {
        int maxHeight = 0;
        for (int i = 0; i < fontList.getSize(); i++) {
            MarqueeFont font = fontList.get(i);
            int height = font.getFontHeight();
            maxHeight = Math.max(height, maxHeight);
        }
        return maxHeight;
    }
}

Класс MarqueeModel

package com.ggl.marquee.model;

import javax.swing.DefaultListModel;

import com.ggl.marquee.runnable.DisplayTextPixelsRunnable;
import com.ggl.marquee.view.MarqueeFrame;

public class MarqueeModel {

    private static final int marqueeWidth = 120;

    private boolean[][] marqueePixels;
    private boolean[][] textPixels;

    private DisplayTextPixelsRunnable dtpRunnable;

    private MarqueeFontFactory fonts;

    private MarqueeFrame frame;

    public MarqueeModel() {
        this.fonts = new MarqueeFontFactory();
        this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
    }

    public void setFrame(MarqueeFrame frame) {
        this.frame = frame;
    }

    public MarqueeFontFactory getFonts() {
        return fonts;
    }

    public DefaultListModel<MarqueeFont> getDefaultListModel() {
        return fonts.getFontList();
    }

    public MarqueeFont getDefaultFont() {
        return fonts.getDefaultFont();
    }

    public void setDefaultFont(MarqueeFont defaultFont) {
        fonts.setDefaultFont(defaultFont);
    }

    public boolean[][] getMarqueePixels() {
        return marqueePixels;
    }

    public boolean getMarqueePixel(int width, int height) {
        return marqueePixels[width][height];
    }

    public int getMarqueeWidth() {
        return marqueeWidth;
    }

    public int getMarqueeHeight() {
        return fonts.getCharacterHeight();
    }

    public boolean[][] getTextPixels() {
        return textPixels;
    }

    public int getTextPixelWidth() {
        return textPixels.length;
    }

    private void startDtpRunnable() {
        dtpRunnable = new DisplayTextPixelsRunnable(frame, this);
        new Thread(dtpRunnable).start();
    }

    public void stopDtpRunnable() {
        if (dtpRunnable != null) {
            dtpRunnable.stopDisplayTextPixelsRunnable();
            dtpRunnable = null;
        }
    }

    public void setTextPixels(boolean[][] textPixels) {
        this.textPixels = textPixels;
        if (textPixels.length < getMarqueeWidth()) {
            this.marqueePixels = copyCharacterPixels(0, textPixels,
                    marqueePixels);
        } else {
            startDtpRunnable();
        }
    }

    public void resetPixels() {
        for (int i = 0; i < getMarqueeWidth(); i++) {
            for (int j = 0; j < getMarqueeHeight(); j++) {
                marqueePixels[i][j] = false;
            }
        }
    }

    public void setAllPixels() {
        for (int i = 0; i < getMarqueeWidth(); i++) {
            for (int j = 0; j < getMarqueeHeight(); j++) {
                marqueePixels[i][j] = true;
            }
        }
    }

    public boolean[][] copyCharacterPixels(int position,
            boolean[][] characterPixels, boolean[][] textPixels) {
        for (int i = 0; i < characterPixels.length; i++) {
            for (int j = 0; j < characterPixels[i].length; j++) {
                textPixels[i + position][j] = characterPixels[i][j];
            }
        }

        return textPixels;
    }

    public void copyTextPixels(int position) {
        for (int i = 0; i < marqueePixels.length; i++) {
            int k = i + position;
            k %= textPixels.length;
            for (int j = 0; j < textPixels[i].length; j++) {
                marqueePixels[i][j] = textPixels[k][j];
            }
        }
    }

}

Класс DisplayAllPixelsRunnable

package com.ggl.marquee.runnable;

import javax.swing.SwingUtilities;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;

public class DisplayAllPixelsRunnable implements Runnable {

    private MarqueeFrame frame;

    private MarqueeModel model;

    public DisplayAllPixelsRunnable(MarqueeFrame frame, MarqueeModel model) {
        this.frame = frame;
        this.model = model;
    }

    @Override
    public void run() {
        model.setAllPixels();
        repaint();

        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {

        }

        model.resetPixels();
        repaint();
    }

    private void repaint() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.repaintMarqueePanel();
            }
        });
    }

}

Класс DisplayTextPixelsRunnable

package com.ggl.marquee.runnable;

import javax.swing.SwingUtilities;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;

public class DisplayTextPixelsRunnable implements Runnable {

    private static int textPixelPosition;

    private volatile boolean running;

    private MarqueeFrame frame;

    private MarqueeModel model;

    public DisplayTextPixelsRunnable(MarqueeFrame frame, MarqueeModel model) {
        this.frame = frame;
        this.model = model;
        textPixelPosition = 0;
    }

    @Override
    public void run() {
        this.running = true;
        while (running) {
            model.copyTextPixels(textPixelPosition);
            repaint();
            sleep();
            textPixelPosition++;
            textPixelPosition %= model.getTextPixelWidth();
        }
    }

    private void repaint() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.repaintMarqueePanel();
            }
        });
    }

    private void sleep() {
        try {
            Thread.sleep(50L);
        } catch (InterruptedException e) {

        }
    }

    public synchronized void stopDisplayTextPixelsRunnable() {
        this.running = false;
    }

}

Класс ControlPanel

package com.ggl.marquee.view;

import java.awt.BorderLayout;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;

import com.ggl.marquee.controller.CreateMarqueeActionListener;
import com.ggl.marquee.controller.FontSelectionListener;
import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;

public class ControlPanel {

    private JButton submitButton;

    private JPanel panel;

    private MarqueeFrame frame;

    private MarqueeModel model;

    public ControlPanel(MarqueeFrame frame, MarqueeModel model) {
        this.frame = frame;
        this.model = model;
        createPartControl();
    }

    private void createPartControl() {
        panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));

        JPanel fontPanel = new JPanel();
        fontPanel.setLayout(new BorderLayout());

        JLabel fontLabel = new JLabel("Fonts");
        fontPanel.add(fontLabel, BorderLayout.NORTH);

        JList<MarqueeFont> fontList = new JList<MarqueeFont>(
                model.getDefaultListModel());
        fontList.setSelectedValue(model.getDefaultFont(), true);
        fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        fontList.setVisibleRowCount(3);

        ListSelectionModel listSelectionModel = fontList.getSelectionModel();
        listSelectionModel.addListSelectionListener(new FontSelectionListener(
                model));

        JScrollPane fontScrollPane = new JScrollPane(fontList);

        fontPanel.add(fontScrollPane, BorderLayout.CENTER);

        panel.add(fontPanel);

        JPanel fieldPanel = new JPanel();

        JLabel fieldLabel = new JLabel("Marquee Text: ");
        fieldPanel.add(fieldLabel);

        JTextField field = new JTextField(30);
        fieldPanel.add(field);

        panel.add(fieldPanel);

        JPanel buttonPanel = new JPanel();

        submitButton = new JButton("Submit");
        submitButton.addActionListener(new CreateMarqueeActionListener(frame,
                model, field));
        buttonPanel.add(submitButton);

        panel.add(buttonPanel);
    }

    public JPanel getPanel() {
        return panel;
    }

    public JButton getSubmitButton() {
        return submitButton;
    }

}

Класс MarqueeFrame

package com.ggl.marquee.view;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.runnable.DisplayAllPixelsRunnable;

public class MarqueeFrame {

    private ControlPanel controlPanel;

    private DisplayAllPixelsRunnable dapRunnable;

    private JFrame frame;

    private MarqueeModel model;

    private MarqueePanel marqueePanel;

    public MarqueeFrame(MarqueeModel model) {
        this.model = model;
        model.setFrame(this);
        createPartControl();
    }

    private void createPartControl() {
        frame = new JFrame();
        // frame.setIconImage(getFrameImage());
        frame.setTitle("Marquee");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                exitProcedure();
            }
        });

        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));

        marqueePanel = new MarqueePanel(model);
        mainPanel.add(marqueePanel);
        controlPanel = new ControlPanel(this, model);
        mainPanel.add(controlPanel.getPanel());

        frame.add(mainPanel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.getRootPane().setDefaultButton(controlPanel.getSubmitButton());
        frame.setVisible(true);

        dapRunnable = new DisplayAllPixelsRunnable(this, model);
        new Thread(dapRunnable).start();
    }

    private void exitProcedure() {
        frame.dispose();
        System.exit(0);
    }

    public void repaintMarqueePanel() {
        marqueePanel.repaint();
    }

}

Класс MarqueePanel

package com.ggl.marquee.view;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JPanel;

import com.ggl.marquee.model.MarqueeModel;

public class MarqueePanel extends JPanel {

    private static final long serialVersionUID = -1677343084333836763L;

    private static final int pixelWidth = 4;
    private static final int gapWidth = 2;
    private static final int totalWidth = pixelWidth + gapWidth;
    private static final int yStart = gapWidth + totalWidth + totalWidth;

    private MarqueeModel model;

    public MarqueePanel(MarqueeModel model) {
        this.model = model;

        int width = model.getMarqueeWidth() * totalWidth + gapWidth;
        int height = model.getMarqueeHeight() * totalWidth + yStart + yStart;
        setPreferredSize(new Dimension(width, height));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        g.setColor(Color.BLACK);
        g.fillRect(0, 0, getWidth(), getHeight());

        int x = gapWidth;
        int y = yStart;

        for (int i = 0; i < model.getMarqueeWidth(); i++) {
            for (int j = 0; j < model.getMarqueeHeight(); j++) {
                if (model.getMarqueePixel(i, j)) {
                    g.setColor(Color.PINK);
                } else {
                    g.setColor(Color.BLACK);
                }

                g.fillRect(x, y, pixelWidth, pixelWidth);
                y += totalWidth;
            }
            y = yStart;
            x += totalWidth;
        }
    }

}
0 голосов
/ 04 июня 2020

Я внес несколько изменений в тот же пример ( Marquee ), опубликованный @Gilbert Le Blan c.

Прежде всего, в примере Marquee Thread.sleep ( ) вместо более современного подхода с TimerTask. Я не могу не подчеркнуть, насколько велика разница между Thread.sleep () и TimerTask.

Thread.sleep () неточен. Насколько неточна, зависит от базовой операционной системы, ее таймеров и планировщиков. Я испытал, что параллельная сборка мусора может привести к чрезмерному сну. Источник: sr c

Это привело к важной проблеме

Функция рисования вызывается непоследовательно вместо ожидаемое фиксированное время для 30 кадров в секунду, что составляет примерно 40 мсек, это порождает некоторые проблемы с опалубкой.
Источник: me

которая частично решается с помощью подхода TimerTask * По крайней мере, в одной из основных операционных систем (Windows) блокировка по таймеру имеет гораздо лучшую точность и надежность, чем спящий режим.
Источник: GameDev. net

Я заметил огромное улучшение, когда переписал тот же пример, но с помощью timerTask, и он определенно более плавный, чем в примере, который вы предоставили (2D Swing);

Я сравнил его следующим образом: скорость Пример с выделением более или менее соответствует скорости вашего 2-го теста качания, установленного на 10, поэтому попробуйте запустить свой тест с int speed = 10; и указанным выше и посмотрите, больше или меньше затвор.

В качестве альтернативы вы можете попробовать запустить собственный пример Marquee и новый с помощью TimerTask, и вы должны увидеть важное существенное различие.

Я думаю, что реализация буферной стратегии с этим - лучший способ до go для достижения действительно плавной прокрутки ... в противном случае всегда есть OpenGL

Классы, которые я изменил:

DisplayTextPixelsRunnable теперь DisplayTextPixelsTimerTask

package com.ggl.marquee.runnable;

import javax.swing.SwingUtilities;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;

import java.util.TimerTask;

public class DisplayTextPixelsTimerTask extends TimerTask {

    private static int textPixelPosition;

    private final MarqueeFrame frame;

    private final MarqueeModel model;

    public DisplayTextPixelsTimerTask(MarqueeFrame frame, MarqueeModel model) {
        this.frame = frame;
        this.model = model;
        textPixelPosition = 0;
    }

    @Override
    public void run() {
        model.copyTextPixels(textPixelPosition);
        repaint();
        textPixelPosition++;
        textPixelPosition %= model.getTextPixelWidth();
    }

    private void repaint() {
        SwingUtilities.invokeLater(frame::repaintMarqueePanel);
    }

}

MarqueeModel

package com.ggl.marquee.model;

import javax.swing.DefaultListModel;

import com.ggl.marquee.runnable.DisplayTextPixelsTimerTask;
import com.ggl.marquee.view.MarqueeFrame;

import java.util.Timer;
import java.util.TimerTask;

public class MarqueeModel {

    private static final int marqueeWidth = 120;

    private static final long FPS_TARGET = 30;
    private static final long DELAY_TIME = (long) (1000.0d / FPS_TARGET);

    private boolean[][] marqueePixels;
    private boolean[][] textPixels;

    private TimerTask dtpRunnable;

    private MarqueeFontFactory fonts;

    private MarqueeFrame frame;

    public MarqueeModel() {
        this.fonts = new MarqueeFontFactory();
        this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
    }

    public void setFrame(MarqueeFrame frame) {
        this.frame = frame;
    }

    public MarqueeFontFactory getFonts() {
        return fonts;
    }

    public DefaultListModel<MarqueeFont> getDefaultListModel() {
        return fonts.getFontList();
    }

    public MarqueeFont getDefaultFont() {
        return fonts.getDefaultFont();
    }

    public void setDefaultFont(MarqueeFont defaultFont) {
        fonts.setDefaultFont(defaultFont);
    }

    public boolean[][] getMarqueePixels() {
        return marqueePixels;
    }

    public boolean getMarqueePixel(int width, int height) {
        return marqueePixels[width][height];
    }

    public int getMarqueeWidth() {
        return marqueeWidth;
    }

    public int getMarqueeHeight() {
        return fonts.getCharacterHeight();
    }

    public boolean[][] getTextPixels() {
        return textPixels;
    }

    public int getTextPixelWidth() {
        return textPixels.length;
    }

    private void startDtpRunnable() {
        dtpRunnable = new DisplayTextPixelsTimerTask(frame, this);
        //running timer task as daemon thread
        Timer timer = new Timer(true);
        timer.scheduleAtFixedRate(dtpRunnable, 0, DELAY_TIME);
    }

    public void stopDtpRunnable() {
        if (dtpRunnable != null) {
            dtpRunnable.cancel();
            dtpRunnable = null;
        }
    }

    public void setTextPixels(boolean[][] textPixels) {
        this.textPixels = textPixels;
        if (textPixels.length < getMarqueeWidth()) {
            this.marqueePixels = copyCharacterPixels(0, textPixels,
                    marqueePixels);
        } else {
            startDtpRunnable();
        }
    }

    public void resetPixels() {
        for (int i = 0; i < getMarqueeWidth(); i++) {
            for (int j = 0; j < getMarqueeHeight(); j++) {
                marqueePixels[i][j] = false;
            }
        }
    }

    public void setAllPixels() {
        for (int i = 0; i < getMarqueeWidth(); i++) {
            for (int j = 0; j < getMarqueeHeight(); j++) {
                marqueePixels[i][j] = true;
            }
        }
    }

    public boolean[][] copyCharacterPixels(int position,
                                           boolean[][] characterPixels, boolean[][] textPixels) {
        for (int i = 0; i < characterPixels.length; i++) {
            for (int j = 0; j < characterPixels[i].length; j++) {
                textPixels[i + position][j] = characterPixels[i][j];
            }
        }

        return textPixels;
    }

    public void copyTextPixels(int position) {
        for (int i = 0; i < marqueePixels.length; i++) {
            int k = i + position;
            k %= textPixels.length;
            for (int j = 0; j < textPixels[i].length; j++) {
                marqueePixels[i][j] = textPixels[k][j];
            }
        }
    }

}

Я не думаю, что можно сделать больше, используя только Java.
Быть честно говоря, пример Marquee - самый плавный из тех, что я видел.

...