I am trying to embed jxbrowser into a opengl game as a gui.(Actually it's Minecraft, but not relevant in this case)
The drawing part is complete. I can draw the browser UI at anywhere as a quad.
BUT, since the browser is running in background, I need to forward the user's mouse position to the browser, or the browser's view.
I've tried the code from JxBrowser's documentation about forwarding mouse events, but looks like it only works with heavyweight widget.
I've tried using heavyweight, but it doesn't provide a way to bake into a BufferedImage.
Here's what I got so far:
public class BrowserScreen extends GuiScreen {
private BrowserView view;
private Browser browser;
public BrowserScreen(BrowserView view, Browser browser) {
this.view = view;
this.browser = browser;
}
int browserWidth = 1260;
int browserHeight = (int) (browserWidth * (float) height / (float) width);
#Override
public void initGui() {
browserHeight = (int) (browserWidth * (float) height / (float) width);
browser.setSize(browserWidth, browserHeight);
view.setSize(browserWidth, browserHeight);
view.setVisible(true);
}
#Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
if (browser == null || view == null) return;
LightWeightWidget component = (LightWeightWidget) view.getComponent(0);
BufferedImage image = (BufferedImage) component.getImage();
if (image == null) return;
//Bind captured image to opengl
int width = image.getWidth();
int height = image.getHeight();
int[] pixels = new int[width * height];
image.getRGB(0, 0, width, height, pixels, 0, width);
ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * 3); //4 for RGBA, 3 for RGB
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = pixels[y * width + x];
buffer.put((byte) ((pixel >> 16) & 0xFF)); // Red component
buffer.put((byte) ((pixel >> 8) & 0xFF)); // Green component
buffer.put((byte) (pixel & 0xFF)); // Blue component
//buffer.put((byte) ((pixel >> 24) & 0xFF)); // Alpha component. Only for RGBA
}
}
buffer.flip(); //FOR THE LOVE OF GOD DO NOT FORGET THIS
// You now have a ByteBuffer filled with the color data of each pixel.
// Now just create a texture ID and bind it. Then you can load it using
// whatever OpenGL method you want, for example:
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, width, height, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer);
//Draw the quad
float pictureRatio = (float) width / (float) height;
float screenRatio = (float) this.width / (float) this.height;
int x = 0;
int y = 0;
if (pictureRatio > screenRatio) {
height = (int) (this.width * ((float) height / (float) width));
y = this.height / 2 - height / 2;
} else {
width = (int) (this.height * ((float) width / (float) height));
x = this.width / 2 - width / 2;
}
Gui.drawScaledCustomSizeModalRect(x, y, 0, 0, width, height, this.width, this.height, width, height);
}
#Override
public void handleMouseInput() throws IOException {
if (browser == null) return;
if (Mouse.isButtonDown(-1)) {
BrowserMouseEvent.BrowserMouseEventBuilder builder = new BrowserMouseEvent.BrowserMouseEventBuilder();
builder.setEventType(MOUSE_PRESSED)
.setButtonType(BrowserMouseEvent.MouseButtonType.PRIMARY)
.setClickCount(1)
.setModifiers(new BrowserKeyEvent.KeyModifiersBuilder().mouseButton().build());
browser.forwardMouseEvent(builder.build());
System.out.println("Left clicked");
}
int i = Mouse.getEventDWheel();
if (i != 0) {
if (i > 1) {
i = 6;
}
if (i < -1) {
i = -6;
}
BrowserMouseEvent.BrowserMouseEventBuilder builder = new BrowserMouseEvent.BrowserMouseEventBuilder();
builder.setEventType(MOUSE_WHEEL)
.setScrollBarPixelsPerLine(25)
.setScrollType(WHEEL_BLOCK_SCROLL)
.setUnitsToScroll(i);
browser.forwardMouseEvent(builder.build());
System.out.println("Scrolled: " + i);
}
super.handleMouseInput();
}
#Override
protected void mouseReleased(int mouseX, int mouseY, int state) {
if (browser == null) return;
BrowserMouseEvent.BrowserMouseEventBuilder builder = new BrowserMouseEvent.BrowserMouseEventBuilder();
builder.setEventType(MOUSE_RELEASED)
.setButtonType(BrowserMouseEvent.MouseButtonType.PRIMARY)
.setClickCount(1)
.setModifiers(BrowserKeyEvent.KeyModifiers.NO_MODIFIERS);
browser.forwardMouseEvent(builder.build());
System.out.println("Released");
}
#Override
public void onGuiClosed() {
dispose();
}
private boolean dispose() {
if (browser != null) {
if (!browser.isDisposed()) browser.dispose();
browser = null;
}
if (view != null) {
if (view.isEnabled()) {
view.setEnabled(false);
view = null;
return true;
}
view = null;
}
return false;
}
public static BrowserScreen open(String url) {
Locale.setDefault(Locale.CHINA);
Browser browser = new Browser(BrowserType.LIGHTWEIGHT);
BrowserPreferences preferences = browser.getPreferences();
preferences.setTransparentBackground(true);
browser.setPreferences(preferences);
BrowserView view = new BrowserView(browser);
final BrowserScreen screen = new BrowserScreen(view, browser);
browser.loadURL(url);
return screen;
}
}
Mouse events forwarding works in the lightweight mode as well. If you run the sample application from the mentioned article replacing the browser constructor with Browser browser = new Browser(BrowserType.LIGHTWEIGHT);, you should see that it works as expected.
I noticed that you don't define mouse event coordinates when composing the BrowserMouseEvent instance.
Please try filling all fields of the BrowserMouseEvent instance(similar to the way described in the article).
I fixed the problem myself. It turns out that:
For every mouse event, I only need to fill in the setX and setY method. The setGlobal method has to be left empty.
The X and Y values forwarded are calculated incorrectly.
For the mouse click event, both the MOUSE_PRESSED and MOUSE_RELEASED events have to be called.
And, here's how I pass the MOUSE_MOVED, since it is not in the according documentation:
BrowserMouseEvent.BrowserMouseEventBuilder builder = new BrowserMouseEvent.BrowserMouseEventBuilder();
builder.setEventType(MOUSE_MOVED)
.setX(browserX)
.setY(browserY)
.setModifiers(new BrowserKeyEvent.KeyModifiersBuilder().mouseButton().build());
browser.forwardMouseEvent(builder.build());
Related
I am trying to write a function that overlays an image at a rectangle with transparency over top of another image, However it doesn't layer the images it just erases the section that I overlay and the transparency cuts through the entire image. Here is my code.
public static void overlayImage(String imagePath, String overlayPath, int x, int y, int width, int height) {
Mat overlay = Imgcodecs.imread(overlayPath, Imgcodecs.IMREAD_UNCHANGED);
Mat image = Imgcodecs.imread(imagePath, Imgcodecs.IMREAD_UNCHANGED);
Rectangle rect = new Rectangle(x, y, width, height);
Imgproc.resize(overlay, overlay, rect.size());
Mat submat = image.submat(new Rect(rect.x, rect.y, overlay.cols(), overlay.rows()));
overlay.copyTo(submat);
Imgcodecs.imwrite(imagePath, image);
}
EDIT: Here are some example pictures:
Before:
After:
Found this function that does exactly what I needed.
public static void overlayImage(Mat background,Mat foreground,Mat output, Point location){
background.copyTo(output);
for(int y = (int) Math.max(location.y , 0); y < background.rows(); ++y){
int fY = (int) (y - location.y);
if(fY >= foreground.rows())
break;
for(int x = (int) Math.max(location.x, 0); x < background.cols(); ++x){
int fX = (int) (x - location.x);
if(fX >= foreground.cols()){
break;
}
double opacity;
double[] finalPixelValue = new double[4];
opacity = foreground.get(fY , fX)[3];
finalPixelValue[0] = background.get(y, x)[0];
finalPixelValue[1] = background.get(y, x)[1];
finalPixelValue[2] = background.get(y, x)[2];
finalPixelValue[3] = background.get(y, x)[3];
for(int c = 0; c < output.channels(); ++c){
if(opacity > 0){
double foregroundPx = foreground.get(fY, fX)[c];
double backgroundPx = background.get(y, x)[c];
float fOpacity = (float) (opacity / 255);
finalPixelValue[c] = ((backgroundPx * ( 1.0 - fOpacity)) + (foregroundPx * fOpacity));
if(c==3){
finalPixelValue[c] = foreground.get(fY,fX)[3];
}
}
}
output.put(y, x,finalPixelValue);
}
}
}
I don't know whether or not this question was answered. At least I didn't find an answer.
So here is a thing: I'm making some space-themed 2D game on android, and I'm testing it on emulator with screen size = 2560x1600. In this game there is a field where space ship is flying. And of course it (a field) must have a beautiful background with high resolution. My background image's resolution is 4500x4500. I want to make my image move in opposite direction relative to camera movement, so thats why I can't use small static image. At the time only a part of this image is visible:
When I tried to draw it I got fps = 1-2 (of course it is low because of the image size):
canvas.drawBitmap(getBigImage(), -x, -y, null);
/* getBigImage() method does nothing but returning
a Bitmap object (no calculation or decoding is performing in there) */
I tried to cut out the needed image from the big one but fps was still low:
Bitmap b = Bitmap.createBitmap(getBigImage(), x, y, sw, sh);
canvas.drawBitmap(b, 0, 0, null);
How can I draw this big bitmap with high fps?
Try
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
which takes a rectangle of pixels from the source image to display in a rectangle on the Canvas. I found this to be faster back when I did a scrolling game.
I was thinking a lot and came up with an idea to divide input bitmap to small chunks and save them to an array. So now to draw that bitmap all I have to do is to draw visible chunks.
Picture:
Big black rectangle means input bitmap, green rectangle means viewport, red rectangle means visible chunks that are drawn
I've wrote an object that does that all (I didn't check it for bugs yet :/). I've tested it and it draws 3000x3000 bitmap with ~45 fps. I'm considering this way as very effective. The object itself may need to be developed more but I think this functionality is enough for my needs. Hope it'll help someone :)
P.S. https://stackoverflow.com/a/25953122/6121671 - used this for inspiration :)
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
public final class DividedBitmap {
private final Bitmap[][] mArray; // array where chunks is stored
private final int mWidth; // original (full) width of source image
private final int mHeight; // original (full) height of source image
private final int mChunkWidth; // default width of a chunk
private final int mChunkHeight; // default height of a chunk
/* Init */
public DividedBitmap(Bitmap src) {
this(new Options(src, 100, 100));
}
public DividedBitmap(Options options) {
mArray = divideBitmap(options);
mWidth = options.source.getWidth();
mHeight = options.source.getHeight();
mChunkWidth = options.chunkWidth;
mChunkHeight = options.chunkHeight;
}
/* Getters */
public int getWidth() {
return mWidth;
}
public int getHeight() {
return mHeight;
}
public Bitmap getChunk(int x, int y) {
if (mArray.length < x && x > 0 && mArray[x].length < y && y > 0) {
return mArray[x][y];
}
return null;
}
/* Methods */
/**
* x, y are viewport coords on the image itself;
* w, h are viewport's width and height.
*/
public void draw(Canvas canvas, int x, int y, int w, int h, Paint paint) {
if (x >= getWidth() || y >= getHeight() || x + w <= 0 || y + h <= 0)
return;
int i1 = x / mChunkWidth; // i1 and j1 are indices of visible chunk that is
int j1 = y / mChunkHeight; // on the top-left corner of the screen
int i2 = (x + w) / mChunkWidth; // i2 and j2 are indices of visible chunk that is
int j2 = (y + h) / mChunkHeight; // on the right-bottom corner of the screen
i2 = i2 >= mArray.length ? mArray.length - 1 : i2;
j2 = j2 >= mArray[i2].length ? mArray[i2].length - 1 : j2;
int offsetX = x - i1 * mChunkWidth;
int offsetY = y - j1 * mChunkHeight;
for (int i = i1; i <= i2; i++) {
for (int j = j1; j <= j2; j++) {
canvas.drawBitmap(
mArray[i][j],
(i - i1) * mChunkWidth - offsetX,
(j - j1) * mChunkHeight - offsetY,
paint
);
}
}
}
/* Static */
public static Bitmap[][] divideBitmap(Bitmap bitmap) {
return divideBitmap(new Options(bitmap, 100, 100));
}
public static Bitmap[][] divideBitmap(Options options) {
Bitmap[][] arr = new Bitmap[options.xCount][options.yCount];
for (int x = 0; x < options.xCount; ++x) {
for (int y = 0; y < options.yCount; ++y) {
int w = Math.min(options.chunkWidth, options.source.getWidth() - (x * options.chunkWidth));
int h = Math.min(options.chunkHeight, options.source.getHeight() - (y * options.chunkHeight));
arr[x][y] = Bitmap.createBitmap(options.source, x * options.chunkWidth, y * options.chunkHeight, w, h);
}
}
return arr;
}
public static final class Options {
final int chunkWidth;
final int chunkHeight;
final int xCount;
final int yCount;
final Bitmap source;
public Options(Bitmap src, int chunkW, int chunkH) {
chunkWidth = chunkW;
chunkHeight = chunkH;
xCount = ((src.getWidth() - 1) / chunkW) + 1;
yCount = ((src.getHeight() - 1) / chunkH) + 1;
source = src;
}
public Options(int xc, int yc, Bitmap src) {
xCount = xc;
yCount = yc;
chunkWidth = src.getWidth() / xCount;
chunkHeight = src.getHeight() / yCount;
source = src;
}
}
}
notes: these shapes are PNGs with transparency.
How do I find out in which of the shapes I'm clicking ?
class Component extends Actor {
private Pixmap pixmap;
private Texture texture;
public Component(String name, FileHandle file) {
this.pixmap = new Pixmap(file);
this.texture = new Texture(pixmap);
setName(name);
setBounds(0, 0, texture.getWidth(), texture.getHeight());
}
#Override
public void draw(Batch batch, float parentAlpha) {
batch.draw(texture, getX(), getY());
}
public Pixmap getPixmap() {
return pixmap;
}
}
**
public class Shapes extends ApplicationAdapter implements InputProcessor ...
**
public void create() {
stage = new Stage(new ScreenViewport());
Group group = new Group();
Component cpn01 = new Component("01", Gdx.files.internal("01.png"));
Component cpn02 = new Component("02", Gdx.files.internal("02.png"));
Component cpn03 = new Component("03", Gdx.files.internal("03.png"));
cpnBlue.setPosition(100f, 50f);
cpnRed.setPosition(150f, 70f);
cpnGreen.setPosition(175f, 100f);
group.addActor(cpn01);
group.addActor(cpn02);
group.addActor(cpn03);
stage.addActor(group);
Gdx.input.setInputProcessor(this);
}
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Group group = (Group) stage.getActors().first();
for (int i = 0; i < group.getChildren().size; i++) {
Component c = (Component) group.getChildren().get(i);
Vector2 vc = c.screenToLocalCoordinates(new Vector2((float) screenX, (float) screenY));
Pixmap pix = c.getPixmap();
int pixel = pix.getPixel((int) vc.x, (int) vc.y);
int transparency = ((pixel & 0xff000000) >> 24);
if ((pixel & 0x000000ff) != 0) {
Gdx.app.log("HIT", c.getName() + ", " + pixel + ", " + transparency);
break;
}
}
return false;
}
I would like to select only the shapes, ignoring the transparent pixels of the PNG.
You can use the getPixel method of your Pixmap with your mouse-position.
int val = pixmap.getPixel(x, y);
Then you can extract the RGBA values from the result
Color.rgba8888ToColor(color, val);
int R = (int)(color.r * 255f);
int G = (int)(color.g * 255f);
int B = (int)(color.b * 255f);
int A = (int)(color.a * 255f);
If the alpha value is greater 0 you could check which of the colors is clicked.
I have researched this for days. It appears that most folks want to place buttons on a transparent canvas or shell. I need to place transparent clickable objects over a canvas/component. In my testing I find that if I don't attempt to put the object on the canvas it simply never displays.
In the final project the application will be showing animated objects with a number of controls that I plan to use images for.
In the example I am trying to work out I have taken Snipped195 which displays a turning torus. I am attempting to place an image label over the torus such that as the torus turns it will show through the area of the label that is transparent. I have set up a gif file that is a red plus sign and has a transparent background. I also picked up some code (can't remember where it came from now) that is part of the paintControl method that looks for transparent pixels and builds a Region object. The region object obviously is doing what it needs to do to define where the image goes. Do I need to apply the region somehow to the image instead of the canvas?
At first when I tried to do this I did get the image displayed. However where the transparent areas where it displayed white. After implementing the paintControl code it at least handled the transparent area properly. Now I need to get the actual image content to display.
I built an object to take care of the image label. I called it TransparentImageLabel. It looks like:
public class TransparentImageLabel extends Canvas {
private Image labelImage;
public TransparentImageLabel(Composite parent, Image image, int style) {
super(parent, style);
this.labelImage = image;
addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
TransparentImageLabel.this.widgetDisposed(e);
}
});
addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
TransparentImageLabel.this.paintControl(e);
}
});
}
private void widgetDisposed(DisposeEvent e) {
}
private void paintControl(PaintEvent event) {
System.out.println("at paint control");
ImageData imgData = this.labelImage.getImageData();
Region region = new Region();
if (imgData.alphaData != null) {
Rectangle pixel = new Rectangle(0, 0, 1, 1);
for (int y = 0; y < imgData.height; y++) {
for (int x = 0; x < imgData.width; x++) {
if (imgData.getAlpha(x, y) == 255) {
pixel.x = imgData.x + x;
pixel.y = imgData.y + y;
region.add(pixel);
}
}
}
} else {
ImageData mask = imgData.getTransparencyMask();
Rectangle pixel = new Rectangle(0, 0, 1, 1);
for (int y = 0; y < mask.height; y++) {
for (int x = 0; x < mask.width; x++) {
if (mask.getPixel(x, y) != 0) {
pixel.x = imgData.x + x;
pixel.y = imgData.y + y;
region.add(pixel);
}
}
}
}
this.setRegion(region);
event.gc.drawImage(labelImage, this.getBounds().x, this.getBounds().y);
region.dispose();
}
}
After adding this to Snipped195 the code looks like:
public class Snippet195 {
private Image redPlus;
static void drawTorus(float r, float R, int nsides, int rings) {
float ringDelta = 2.0f * (float) Math.PI / rings;
float sideDelta = 2.0f * (float) Math.PI / nsides;
float theta = 0.0f, cosTheta = 1.0f, sinTheta = 0.0f;
for (int i = rings - 1; i >= 0; i--) {
float theta1 = theta + ringDelta;
float cosTheta1 = (float) Math.cos(theta1);
float sinTheta1 = (float) Math.sin(theta1);
GL11.glBegin(GL11.GL_QUAD_STRIP);
float phi = 0.0f;
for (int j = nsides; j >= 0; j--) {
phi += sideDelta;
float cosPhi = (float) Math.cos(phi);
float sinPhi = (float) Math.sin(phi);
float dist = R + r * cosPhi;
GL11.glNormal3f(cosTheta1 * cosPhi, -sinTheta1 * cosPhi, sinPhi);
GL11.glVertex3f(cosTheta1 * dist, -sinTheta1 * dist, r * sinPhi);
GL11.glNormal3f(cosTheta * cosPhi, -sinTheta * cosPhi, sinPhi);
GL11.glVertex3f(cosTheta * dist, -sinTheta * dist, r * sinPhi);
}
GL11.glEnd();
theta = theta1;
cosTheta = cosTheta1;
sinTheta = sinTheta1;
}
}
private Snippet195() {
final Display display = new Display();
Shell shell = new Shell(display, SWT.NO_REDRAW_RESIZE);
shell.setLayout(new FillLayout());
Composite comp = new Composite(shell, SWT.NONE);
comp.setLayout(new FillLayout());
GLData data = new GLData();
data.doubleBuffer = true;
redPlus = new Image(shell.getDisplay(), new ImageData(
Snippet237.class.getResourceAsStream("/red-plus.png")));
final GLCanvas canvas = new GLCanvas(comp, SWT.NONE, data);
canvas.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
e.gc.setAlpha(15);
e.gc.drawImage(Snippet195.this.redPlus, 0, 0);
}
});
canvas.setCurrent();
try {
GLContext.useContext(canvas);
} catch (LWJGLException e) {
e.printStackTrace();
}
canvas.addListener(SWT.Resize, new Listener() {
public void handleEvent(Event event) {
Rectangle bounds = canvas.getBounds();
float fAspect = (float) bounds.width / (float) bounds.height;
canvas.setCurrent();
try {
GLContext.useContext(canvas);
} catch (LWJGLException e) {
e.printStackTrace();
}
GL11.glViewport(0, 0, bounds.width, bounds.height);
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glLoadIdentity();
GLU.gluPerspective(45.0f, fAspect, 0.5f, 400.0f);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glLoadIdentity();
}
});
GL11.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GL11.glColor3f(1.0f, 0.0f, 0.0f);
GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);
GL11.glClearDepth(1.0);
GL11.glLineWidth(2);
GL11.glEnable(GL11.GL_DEPTH_TEST);
TransparentImageLabel redPlusLabel = new TransparentImageLabel(canvas,
redPlus, SWT.NONE);
redPlusLabel.setSize(48, 48);
redPlusLabel.setLocation(500, 200);
shell.setText("SWT/LWJGL Example");
shell.setSize(880, 720);
shell.open();
final Runnable run = new Runnable() {
int rot = 0;
public void run() {
if (!canvas.isDisposed()) {
canvas.setCurrent();
try {
GLContext.useContext(canvas);
} catch (LWJGLException e) {
e.printStackTrace();
}
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT
| GL11.GL_DEPTH_BUFFER_BIT);
GL11.glClearColor(.3f, .5f, .8f, 1.0f);
GL11.glLoadIdentity();
GL11.glTranslatef(0.0f, 0.0f, -10.0f);
float frot = rot;
GL11.glRotatef(0.15f * rot, 2.0f * frot, 10.0f * frot, 1.0f);
GL11.glRotatef(0.3f * rot, 3.0f * frot, 1.0f * frot, 1.0f);
rot++;
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
GL11.glColor3f(0.9f, 0.9f, 0.9f);
drawTorus(1, 1.9f + ((float) Math.sin((0.004f * frot))), 25, 75);
canvas.swapBuffers();
display.asyncExec(this);
}
}
};
canvas.addListener(SWT.Paint, new Listener() {
public void handleEvent(Event event) {
run.run();
}
});
display.asyncExec(run);
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
public static void main(String[] args) {
new Snippet195();
}
}
I have to be close. The areas of the image that are defined as transparent are being drawn as transparent. But I'm not getting anything but a white plus instead of the red that is in the image.
The problem is in your TransparentImageLabel#paintControl(..) method. Correct the second last line to the following:
event.gc.drawImage(labelImage, 0, 0);
Since you are drawing within the context of the canvas so the coordinates you specify for location should be relative to that Canvas. You are currently using the location of canvas which is returned relative to it's parent.
plese look at my code snippets , wha is wrong with it , it frrezes GUI when the Swing timer stats which is repeteadly paints on the jpnael ??
class WaveformPanel extends JPanel {
Timer graphTimer = null;
AudioInfo helper = null;
WaveformPanel() {
setPreferredSize(new Dimension(200, 80));
setBorder(BorderFactory.createLineBorder(Color.BLACK));
graphTimer = new Timer(15, new TimerDrawing());
}
/**
*
*/
private static final long serialVersionUID = 969991141812736791L;
protected final Color BACKGROUND_COLOR = Color.white;
protected final Color REFERENCE_LINE_COLOR = Color.black;
protected final Color WAVEFORM_COLOR = Color.red;
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int lineHeight = getHeight() / 2;
g.setColor(REFERENCE_LINE_COLOR);
g.drawLine(0, lineHeight, (int) getWidth(), lineHeight);
if (helper == null) {
return;
}
drawWaveform(g, helper.getAudio(0));
}
protected void drawWaveform(Graphics g, int[] samples) {
if (samples == null) {
return;
}
int oldX = 0;
int oldY = (int) (getHeight() / 2);
int xIndex = 0;
int increment = helper.getIncrement(helper
.getXScaleFactor(getWidth()));
g.setColor(WAVEFORM_COLOR);
int t = 0;
for (t = 0; t < increment; t += increment) {
g.drawLine(oldX, oldY, xIndex, oldY);
xIndex++;
oldX = xIndex;
}
for (; t < samples.length; t += increment) {
double scaleFactor = helper.getYScaleFactor(getHeight());
double scaledSample = samples[t] * scaleFactor;
int y = (int) ((getHeight() / 2) - (scaledSample));
g.drawLine(oldX, oldY, xIndex, y);
xIndex++;
oldX = xIndex;
oldY = y;
}
}
public void setAnimation(boolean turnon) {
if (turnon) {
graphTimer.start();
} else {
graphTimer.stop();
}
}
class TimerDrawing implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
byte[] bytes = captureThread.getTempBuffer();
if (helper != null) {
helper.setBytes(bytes);
} else {
helper = new AudioInfo(bytes);
}
repaint();
}
}
}
I am calling setAnimation of WaveFormPanel from its parent class.when animation starts it does not draw anything but freezes. please , give me solution.
Thank You
Mihir Parekh
The java.swingx.Timer calls the ActionPerformed within the EDT. The question then is, what's taking the time to render. It could be the call to captureThread.getTempBuffer it could be the construction of the help, but I suspect it's just the share amount of data you are trying to paint.
Having playing with this recently, it takes quite a bit of time to process the waveform.
One suggestion might be to reduce the number of samples that you paint. Rather then painting each one, maybe paint every second or forth sample point depending on the width of the component. You should still get the same jest but without all the work...
UPDATED
All samples, 2.18 seconds
Every 4th sample, 0.711 seconds
Every 8th sample, 0.450 seconds
Rather then paint in response to the timer, maybe you need to paint in response to batches of data.
As your loader thread has a "chunk" of data, may be paint it then.
As HoverCraftFullOfEels suggested, you could paint this to a BufferedImage first and then paint that to the screen...
SwingWorker might be able to achieve this for you
UPDATED
This is the code I use to paint the above samples.
// Samples is a 2D int array (int[][]), where the first index is the channel, the second is the sample for that channel
if (samples != null) {
Graphics2D g2d = (Graphics2D) g;
int length = samples[0].length;
int width = getWidth() - 1;
int height = getHeight() - 1;
int oldX = 0;
int oldY = height / 2;
int frame = 0;
// min, max is the min/max range of the samples, ie the highest and lowest samples
int range = max + (min * -2);
float scale = (float) height / (float) range;
int minY = Math.round(((height / 2) + (min * scale)));
int maxY = Math.round(((height / 2) + (max * scale)));
LinearGradientPaint lgp = new LinearGradientPaint(
new Point2D.Float(0, minY),
new Point2D.Float(0, maxY),
new float[]{0f, 0.5f, 1f},
new Color[]{Color.BLUE, Color.RED, Color.BLUE});
g2d.setPaint(lgp);
for (int sample : samples[0]) {
if (sample % 64 == 0) {
int x = Math.round(((float) frame / (float) length) * width);
int y = Math.round((height / 2) + (sample * scale));
g2d.drawLine(oldX, oldY, x, y);
oldX = x;
oldY = y;
}
frame++;
}
}
I use an AudioStream stream to load a Wav file an produce the 2D samples.
I'm guessing that your wave drawing code, which is being called from within a paintComponent(...) method is taking longer than you think and is tying up both Swing painting and the EDT.
If this were my code, I'd consider drawing my waves to BufferedImages once, making ImageIcons from these images and then simply swapping icons in my Swing Timer.