Question

I am currently trying to paint a Waveform in very high resolution (due to zooming). The Waveform is drawn in a JScrollPane therefore. I want to be able to paint about 50.000-60.000 pixels width with it.

Unfortunately it stops painting properly at about 34000 pixel width. The issue is that it doesn't paint ca. the first screen size anymore, but the rest is drawn properly. As I only have very little experience in graphical stuff I thought you might be able to help me to decide how to fix this as best as possible.

I thought about dealing with it via repainting the first screen size (e.g. with repaint(Rectangle)) or maybe with parting the picture into 3 or more frames. If I choose the 2nd option I don't know if I should just part it an paint it all together or only paint it when its visible on the Viewport. Or maybe there is another better solution I cant figure out?

I hope you can help me with this and save me loads of hours to try out everything. Thanks in advance.

So here is the requested executable. You can see improper drawing at about 34.000 pixels width. Current pixels can be read in System.out . Drawing doesnt work with mp3, as not supported. I suggest tryouts with .wav.

Main Class:

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import java.awt.GridBagLayout;

import java.awt.GridBagConstraints;

public class Main {

private JFrame mainFrame;
private JPanel upperPanel;

/**
 * Launch the application.
 */
public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
                Main window = new Main();
                window.mainFrame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}

/**
 * Initialize Application
 */
public Main() {

    initializeMainFrame();
    initializePanels();
}

private void initializePanels() {

    upperPanel = new MainPanel();

    upperPanel.setPreferredSize(new Dimension(1000, 500));

    GridBagConstraints c = new GridBagConstraints();
    c.anchor = GridBagConstraints.NORTH;
    c.weightx = 1.0;
    c.weighty = 1.0;
    c.fill = GridBagConstraints.BOTH;
    c.gridy = 0;
    c.gridwidth = GridBagConstraints.REMAINDER;

    mainFrame.add(upperPanel, c);

}

private void initializeMainFrame() {

    mainFrame = new JFrame("Waveform Example");
    mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    GraphicsEnvironment ge = GraphicsEnvironment
            .getLocalGraphicsEnvironment();
    Rectangle gebounds = ge.getMaximumWindowBounds();
    mainFrame.setSize(gebounds.getSize());
    mainFrame.setLocationRelativeTo(null);
    mainFrame.setVisible(true);
    mainFrame.setLayout(new GridBagLayout());

    JMenuBar menuBar = new JMenuBar();
    JMenu fileMenu = new JMenu("File");

    JMenuItem importAudio = new JMenuItem("Import Audio");

    menuBar.setLayout(new GridBagLayout());
    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.HORIZONTAL;
    c.anchor = GridBagConstraints.WEST;
    c.weightx = 0.3;
    c.weighty = 0.3;
    c.gridx = 0;
    c.gridy = 0;

    mainFrame.setJMenuBar(menuBar);
    menuBar.add(fileMenu, c);
    c.gridx = 1;
    c.gridy = 0;

    fileMenu.add(importAudio);

    importAudio.addActionListener(new importAudioActionListener());

}

private class importAudioActionListener implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent event) {

        File f = getFile();


        if (f != null) {

            AudioInfo audioInfo = createAudioInfo(f);

            mainFrame.remove(upperPanel);

            upperPanel = new MainPanel(audioInfo);

            upperPanel.setPreferredSize(new Dimension(1000, 500));

            GridBagConstraints c = new GridBagConstraints();
            c.anchor = GridBagConstraints.NORTH;
            c.gridy = 0;
            c.weightx = 1.0;
            c.weighty = 1.0;
            c.fill = GridBagConstraints.BOTH;
            c.gridwidth = GridBagConstraints.REMAINDER;

            mainFrame.add(upperPanel, c);

            mainFrame.pack();

        }
    }

    private AudioInfo createAudioInfo(File f) {
        AudioInputStream audioInputStream = null;

        try {

            audioInputStream = AudioSystem.getAudioInputStream(f);

        } catch (UnsupportedAudioFileException e1) {

            System.out.println("Invalid Audio Format");
        } catch (IOException e1) {

            System.out.println("Invalid Input File");
        }

        AudioInfo retInfo = new AudioInfo(audioInputStream,
                (int) f.length());
        return retInfo;
    }

    private File getFile() {
        // New file chooser only shows and accepts MP3 files.
        JFileChooser fc = new JFileChooser();
        fc.setAcceptAllFileFilterUsed(false);
        fc.showOpenDialog(null);

        File f = null;
        try {
            f = fc.getSelectedFile();
        } catch (Exception fnfe) {
            f = null;
            System.out.println("File not found!");
        }
        return f;
    }
}

  }

Panel that contains JPanel:

   import java.awt.BorderLayout;
  import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
 import java.awt.Rectangle;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
 import java.awt.event.MouseWheelEvent;
 import java.awt.event.MouseWheelListener;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;

   public class MainPanel extends JPanel implements MouseWheelListener,
    ComponentListener {


private boolean finishedZoom = true;
private int mouseX;
private static final long serialVersionUID = 1L;
private AudioInfo audioInfo;
private int scale = 1;
private Dimension panelSize;
private int mouseXScaled;
private int mouseYScaled;

private JScrollPane scrollPane;
private int sizeNormalizer = 150;
private JPanel thisPanel = this;
private JPanel content;

public MainPanel() {

}

public MainPanel(AudioInfo audioInfo) {
    this.audioInfo = audioInfo;
    this.setLayout(new BorderLayout());

    panelSize = new Dimension(1000, 500);

    content = getContent();

    scrollPane = new JScrollPane(content);
    scrollPane.setPreferredSize(panelSize);
    scrollPane
            .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);


    this.add(scrollPane, BorderLayout.CENTER);
    this.setPreferredSize(panelSize);

    content.addMouseWheelListener(this);
    content.addComponentListener(this);

}

private JPanel getContent() {
    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.BOTH;
    c.weightx = 1.0;
    c.weighty = 1.0;
    JPanel retContent = new JPanel(false);
    retContent.setPreferredSize(panelSize);
    retContent.setLayout(new GridBagLayout());

    WaveformPanel waveformPanel = new WaveformPanel(audioInfo);
    waveformPanel.setPreferredSize(panelSize);

    retContent.setBackground(Color.green);

    c.gridwidth = GridBagConstraints.REMAINDER; // end row
    retContent.add(waveformPanel, c);

    return retContent;
}

public void mouseWheelMoved(MouseWheelEvent e) {

    boolean changed = false;
    double notches = e.getWheelRotation();
    if (e.isControlDown() && finishedZoom) {

        int newScale = (int) (scale + notches * (-1) * 2);

        int newWidth = (int) ((thisPanel.getPreferredSize().getWidth()) * newScale);

        if (newWidth > content.getPreferredSize().getWidth()) {
            System.out.println("new width original: " + newWidth);
            content.setVisible(false);
            content.setPreferredSize(new Dimension(
                    newWidth,
                    (int) ((thisPanel.getPreferredSize().getHeight() - sizeNormalizer) / 3 * 2)));
            content.setVisible(true);
            mouseXScaled = e.getX() / scale * newScale;
            mouseYScaled = e.getY() / scale * newScale;
            scale = newScale;
            changed = true;

        } else if (newWidth < content.getPreferredSize().getWidth()
                && newWidth > thisPanel.getWidth()) {

            content.setVisible(false);
            content.setPreferredSize(new Dimension(
                    newWidth,
                    (int) ((thisPanel.getPreferredSize().getHeight() - sizeNormalizer) / 3 * 2)));
            content.setVisible(true);
            mouseXScaled = e.getX() / scale * newScale;
            mouseYScaled = e.getY() / scale * newScale;
            scale = newScale;
            changed = true;

        } else if (newWidth <= thisPanel.getWidth()) {
            newWidth = (int) (thisPanel.getPreferredSize().getWidth());
            newScale = 1;
            content.setVisible(false);
            content.setPreferredSize(new Dimension(
                    newWidth,
                    (int) ((thisPanel.getPreferredSize().getHeight() - sizeNormalizer) / 3 * 2)));
            content.setVisible(true);
            mouseXScaled = e.getX() / scale * newScale;
            mouseYScaled = e.getY() / scale * newScale;
            scale = newScale;
        }
        if (changed) {
            finishedZoom = false;
        }
        mouseX = e.getX();

    } else if (!e.isControlDown()) {
        int scrollBarValue =     scrollPane.getHorizontalScrollBar().getValue();
        Rectangle viewRect = scrollPane.getViewport().getViewRect();

        scrollPane
                .getHorizontalScrollBar()
                .setValue(
                        (int) ((int) scrollBarValue       +           ((viewRect.width - 100) * notches)));

    }

}

public int getHorizontalScroll() {
    return scrollPane.getHorizontalScrollBar().getValue();
}

@Override
public void componentHidden(ComponentEvent arg0) {
    // TODO Auto-generated method stub

}

@Override
public void componentMoved(ComponentEvent arg0) {
    // TODO Auto-generated method stub

}

@Override
public void componentResized(ComponentEvent arg0) {

    if (mouseXScaled != 0 && mouseYScaled != 0) {

        int scrollBarVal = scrollPane.getHorizontalScrollBar().getValue();

        int newX = (int) (scrollBarVal + mouseXScaled - mouseX);
        scrollPane.getHorizontalScrollBar().setValue(newX);
        finishedZoom = true;

    }

}

@Override
public void componentShown(ComponentEvent arg0) {
    // TODO Auto-generated method stub

}
 }

AudioInfo Class:

import java.io.IOException;
import javax.sound.sampled.AudioInputStream;

  public class AudioInfo {
private static final int NUM_BITS_PER_BYTE = 8;

private AudioInputStream encodedInputSream;
private int[][] encodedSamplesContainer;
private byte[] encodedBuffer;

// cached values
private int sampleMax = 0;
private int sampleMin = 0;
private double biggestSample;

public AudioInfo(AudioInputStream encodedInputStream, int fileSize) {

    encodedBuffer = new byte[fileSize];
    this.encodedInputSream = encodedInputStream;
    encodedBuffer = createSampleArrayCollection(encodedInputStream,
            encodedBuffer);
    encodedSamplesContainer = getSampleArray(encodedBuffer);

    if (sampleMax > sampleMin) {
        biggestSample = sampleMax;
    } else {
        biggestSample = Math.abs(((double) sampleMin));
    }

}

protected int getNumberOfChannels() {


    return 2;

}

/**
 * Reads the audio input stream into a tmp array and then inserts the tmp
 * array into a buffer array. Resets the mark of the audio input stream
 * after finish buffering. Then cuts the array from fileSize*10 to the final
 * size.
 */
private byte[] createSampleArrayCollection(AudioInputStream inputStream,
        byte[] inBuffer) {
    byte[] buffer = new byte[inBuffer.length];
    int sumReadBytes = 0;
    try {
    //  inputStream.mark(Integer.MAX_VALUE);
        //inputStream.reset();

        boolean end = false;

        while (!end) {
            int available = inputStream.available();
            if (available <= 0) {
                end = true;
            }
            if (!end) {
                byte[] tmp = new byte[available];

                int readBytes = inputStream.read(tmp);

                tmp = cutArray(tmp, readBytes);
                insertArray(buffer, tmp, sumReadBytes);
                sumReadBytes += readBytes;

            }

        }

        //inputStream.reset();

    } catch (IOException e) {
        e.printStackTrace();
    }

    buffer = cutArray(buffer, sumReadBytes);

    return buffer;

}

/**
 * 
 * @param cutThis
 *            array that has to be cut
 * @param cutPoint
 *            index at which the array will be cut off
 * @return the buffer array cut off at the point of cutpoint
 */
private byte[] cutArray(byte[] cutThis, int cutPoint) {

    byte[] tmp = new byte[cutPoint];

    for (int i = 0; i < tmp.length; i++) {
        tmp[i] = cutThis[i];

    }

    return tmp;
}

/**
 * 
 * @param insertIntoThis
 *            the array you want to insert in the other
 * @param tmp
 *            the array that is going to be inserted
 */
private byte[] insertArray(byte[] insertIntoThis, byte[] tmp,
        int nextEmptyField) {
    for (int i = 0; i < tmp.length; i++) {
        insertIntoThis[nextEmptyField] = tmp[i];
        nextEmptyField++;

    }
    return insertIntoThis;
}

/**
 * 
 * @param eightBitByteArray
 *            Array of an eight bit byte array.
 * @return int audio information array for every channel.
 */
private int[][] getSampleArray(byte[] eightBitByteArray) {
    int[][] toReturn = new int[getNumberOfChannels()][eightBitByteArray.length
            / (2 * getNumberOfChannels()) + 1];
    int index = 0;

    // loop through the byte[]
    for (int t = 0; t + 4 < eightBitByteArray.length;) {
        // for each iteration, loop through the channels
        for (int a = 0; a < getNumberOfChannels(); a++) {
            // do the byte to sample conversion
            // see AmplitudeEditor for more info

            int low = (int) eightBitByteArray[t];
            t++;
            int high = (int) eightBitByteArray[t];
            t++;
            int sample = (high << 8) + (low & 0x00ff);

            if (sample < sampleMin) {
                sampleMin = sample;
            } else if (sample > sampleMax) {
                sampleMax = sample;
            }

            // set the value.

            toReturn[a][index] = sample;

        }
        index++;

    }
    return toReturn;
}

/**
 * 
 * @param panelHeight
 * @return calculated yScaleFactor
 */
public double getYScaleFactor(int panelHeight) {
    return (panelHeight / (biggestSample * 2 * 1.5));
}

/**
 * 
 * @param channel
 *            number of the channel you want the audio information of
 * @return int array of the audio information of the given channel.
 */
protected int[] getAudio(int channel) {
    return encodedSamplesContainer[channel];
}

/**
 * 
 * @param xScale
 * @return calculates the increment for given xScale
 */
protected int getIncrement(double xScale) {
    try {
        int increment = (int) (encodedSamplesContainer[0].length / (encodedSamplesContainer[0].length * xScale));
        return increment;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return -1;
}

  }

Waveform Panel Class:

    import javax.swing.*;

   import java.awt.*;

   public class WaveformPanel extends JPanel {

private static final long serialVersionUID = 1L;
private static final Color BACKGROUND_COLOR = Color.black;
private static final Color REFERENCE_LINE_COLOR = Color.blue;
private static final Color WAVEFORM_COLOR = Color.blue;

private AudioInfo helper;
private int[] samples;

public WaveformPanel(AudioInfo helper) {
    super();
    this.helper = helper;
    setBackground(BACKGROUND_COLOR);
    samples = helper.getAudio(0);

}

/**
 * Paints the component of the melted channel audio data.
 */
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    int lineHeight = getHeight() / 2;
    g.setColor(REFERENCE_LINE_COLOR);
    g.drawLine(0, lineHeight, (int) getWidth(), lineHeight);

    drawWaveform(g, samples);

}

protected double getXScaleFactor(int panelWidth) {
    double width = (double) panelWidth;
    return (width / ((double) samples.length));
}

private double getIncrement(double xScale) {
    try {
        double increment = (samples.length / (samples.length * xScale));
        return increment;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return -1;
}

/**
 * @param g
 *            graphic of this panel
 * @param samples
 *            audio samples of a channel
 * 
 *            Draws a waveform with given input on a graphic.
 */
protected void drawWaveform(Graphics g, int[] samples) {

    int buffer = 30;
    if (samples == null) {
        return;
    }

    double oldX = 0;
    double xIndex = 0;

    double increment = getIncrement(getXScaleFactor(getWidth() - buffer * 2));

    g.setColor(WAVEFORM_COLOR);
    System.out.println("width: " + this.getWidth());

    double t = 0;

    int drawLength = samples.length;

    for (; t < drawLength; t += increment) {
        double scaleFactor = helper.getYScaleFactor(getHeight());
        double scaledSample = samples[(int) t] * scaleFactor;
        double y = ((getHeight() / 2) - (scaledSample));
        double yMirror = ((getHeight() / 2) + scaledSample);

        g.drawLine((int) (oldX + buffer), (int) yMirror,
                (int) (xIndex + buffer), (int) y);

        xIndex++;
        oldX = xIndex;
    }

}

   }
Was it helpful?

Solution

As an alternative, see this MCTaRE that successfully renders an image that is twice that width. Scroll it to half width (or any width for that matter) to see ..the image with no artifacts.

Note that I called setPreferredSize in that example to save a few code lines, but see Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? (Yes.)

import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

class BigImageWaveform {

    public static void main(String[] args) {
        final BufferedImage bi = new BufferedImage(
            2*34000, 500, BufferedImage.TYPE_INT_RGB);
        draw(bi);
        Runnable r = new Runnable() {
            @Override
            public void run() {
                JScrollPane jsp = new JScrollPane(
                    new JLabel(new ImageIcon(bi)),
                    JScrollPane.VERTICAL_SCROLLBAR_NEVER,
                    JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
                Dimension d = jsp.getPreferredSize();
                jsp.setPreferredSize(new Dimension(1000, (int)d.getHeight()));
                JOptionPane.showMessageDialog(null, jsp);
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency
        SwingUtilities.invokeLater(r);
    }

    public static void draw(BufferedImage bi) {
        Graphics2D g = bi.createGraphics();
        int w = bi.getWidth();
        int h = bi.getHeight();
        GradientPaint gp = new GradientPaint(
            0f,0f,Color.RED,
            101f,0f,Color.GREEN,true);
        g.setPaint(gp);
        g.fillRect(0,0,w,h);
        gp = new GradientPaint(
            0f,0f,new Color(0,0,255,128),
            97f,103f,new Color(220,0,220,164), true);
        g.setPaint(gp);
        g.fillRect(0,0,w,h);
        gp = new GradientPaint(
            0f,0f,new Color(0,0,0,0),
            (float)w,0f,new Color(0,0,0,128), true);
        g.setPaint(gp);
        g.fillRect(0,0,w,h);
        g.dispose();
    }
}

OTHER TIPS

After testing this on two more potent Windows Systems I came to the opinion that its either a Linux problem or a performance problem, as my laptop is about 2 years old and was pretty cheap.

If anybody could test it out on a Linux System it would be great. Otherwise I am going to mark this issue as answered.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top