I need to create a graph that is similar to a Baccarat trend like this
What could be the best approach? I am thinking of using RecyclerView with GridLayout but I have no idea how to plot it this way. Any library that support this kind of graph?
Try creating your own android View that can take some data and draw it
An example of your draw method could be
public void draw(Canvas canvas){
//draw grid
int spacing = 10; //costant cell size
//use (height/rows) and (width/cols) as spacing to have costant cols and rows instead
for(int y = 0; y<height; y+=spacing){
canvas.drawLine(0,y,width,y,paint);
}
for(int x = 0; x<width; x+=spacing){
canvas.drawLine(x, 0, x,height,paint);
}
//draw your data
}
I want to make a waveform drawing for an audio recorder in Android. The usual one with lines/bars, like this one:
More importantly, I want it live, while the song is being recorded. My app already computes the RMS through AudioRecord. But I am not sure which is the best approach for the actual drawing in terms of processing, resources, battery, etc.
The Visualizer does not show anything meaningful, IMO (are those graphs more or less random stuff??).
I've seen the canvas approach and the layout approach (there are probably more?). In the layout approach you add thin vertical layouts in a horizontal layout. The advantage is that you don't need to redraw the whole thing each 1/n secs, you just add one layout each 1/n secs... but you need hundreds of layouts (depending on n). In the canvas layout, you need to redraw the whole thing (right??) n times per second. Some even create bitmaps for each drawing...
So, which is cheaper, and why? Is there anything better nowadays? How much frequency update (i.e., n) is too much for generic low end devices?
EDIT1
Thanks to the beautiful trick #cactustictacs taught me in his answer, I was able to implement this with ease. Yet, the image is strangely rendered kind of "blurry by movement":
The waveform runs from right to left. You can easily see the blur movement, and the left-most and right-most pixels get "contaminated" by the other end. I guess I can just cut both extremes...
This renders better if I make my Bitmap bigger (i.e., making widthBitmap bigger), but then the onDraw will be heavier...
This is my full code:
package com.floritfoto.apps.ave;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import java.util.Arrays;
public class Waveform extends androidx.appcompat.widget.AppCompatImageView {
//private float lastPosition = 0.5f; // 0.5 for drawLine method, 0 for the others
private int lastPosition = 0;
private final int widthBitmap = 50;
private final int heightBitmap = 80;
private final int[] transpixels = new int[heightBitmap];
private final int[] whitepixels = new int[heightBitmap];
//private float top, bot; // float for drawLine method, int for the others
private int aux, top;
//private float lpf;
private int width = widthBitmap;
private float proportionW = (float) (width/widthBitmap);
Boolean firstLoopIsFinished = false;
Bitmap MyBitmap = Bitmap.createBitmap(widthBitmap, heightBitmap, Bitmap.Config.ARGB_8888);
//Canvas canvasB = new Canvas(MyBitmap);
Paint MyPaint = new Paint();
Paint MyPaintTrans = new Paint();
Rect rectLbit, rectRbit, rectLdest, rectRdest;
public Waveform(Context context, AttributeSet attrs) {
super(context, attrs);
MyPaint.setColor(0xffFFFFFF);
MyPaint.setStrokeWidth(1);
MyPaintTrans.setColor(0xFF202020);
MyPaintTrans.setStrokeWidth(1);
Arrays.fill(transpixels, 0xFF202020);
Arrays.fill(whitepixels, 0xFFFFFFFF);
}
public void drawNewBar() {
// For drawRect or drawLine
/*
top = ((1.0f - Register.tone) * heightBitmap / 2.0f);
bot = ((1.0f + Register.tone) * heightBitmap / 2.0f);
// Using drawRect
//if (firstLoopIsFinished) canvasB.drawRect(lastPosition, 0, lastPosition+1, heightBitmap, MyPaintTrans); // Delete last stuff
//canvasB.drawRect(lastPosition, top, lastPosition+1, bot, MyPaint);
// Using drawLine
if (firstLoopIsFinished) canvasB.drawLine(lastPosition, 0, lastPosition, heightBitmap, MyPaintTrans); // Delete previous stuff
canvasB.drawLine(lastPosition ,top, lastPosition, bot, MyPaint);
*/
// Using setPixel (no tiene sentido, mucho mejor setPixels.
/*
int top = (int) ((1.0f - Register.tone) * heightBitmap / 2.0f);
int bot = (int) ((1.0f + Register.tone) * heightBitmap / 2.0f);
if (firstLoopIsFinished) {
for (int i = 0; i < top; ++i) {
MyBitmap.setPixel(lastPosition, i, 0xFF202020);
MyBitmap.setPixel(lastPosition, heightBitmap - i-1, 0xFF202020);
}
}
for (int i = top ; i < bot ; ++i) {
MyBitmap.setPixel(lastPosition,i,0xffFFFFFF);
}
//System.out.println("############## "+top+" "+bot);
*/
// Using setPixels. Works!!
top = (int) ((1.0f - Register.tone) * heightBitmap / 2.0f);
if (firstLoopIsFinished)
MyBitmap.setPixels(transpixels,0,1,lastPosition,0,1,heightBitmap);
MyBitmap.setPixels(whitepixels, top,1, lastPosition, top,1,heightBitmap-2*top);
lastPosition++;
aux = (int) (width - proportionW * (lastPosition));
rectLbit.right = lastPosition;
rectRbit.left = lastPosition;
rectLdest.right = aux;
rectRdest.left = aux;
if (lastPosition >= widthBitmap) { firstLoopIsFinished = true; lastPosition = 0; }
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
proportionW = (float) width/widthBitmap;
rectLbit = new Rect(0, 0, widthBitmap, heightBitmap);
rectRbit = new Rect(0, 0, widthBitmap, heightBitmap);
rectLdest = new Rect(0, 0, width, h);
rectRdest = new Rect(0, 0, width, h);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawNewBar();
canvas.drawBitmap(MyBitmap, rectLbit, rectRdest, MyPaint);
canvas.drawBitmap(MyBitmap, rectRbit, rectLdest, MyPaint);
}
}
EDIT2
I was able to prevent the blurring just using null as Paint in the canvas.drawBitmap:
canvas.drawBitmap(MyBitmap, rectLbit, rectRdest, null);
canvas.drawBitmap(MyBitmap, rectRbit, rectLdest, null);
No Paints needed.
Your basic custom view approach would be to implement onDraw and redraw your current data each frame. You'd probably keep some kind of circular Buffer holding your most recent n amplitude values, so each frame you'd iterate over those, and use drawRect to draw the bars (you'd calculate things like width, height scaling, start and end positions etc in onSizeChanged, and use those values when defining the coordinates for the Rects).
That in itself might be fine! The only way you can really tell how expensive draw calls are is to benchmark them, so you could try this approach out and see how it goes. Profile it to see how much time it takes, how much the CPU spikes etc.
There are a few things you can do to make onDraw as efficient as possible, mostly things like avoiding object allocations - so watch out for loop functions that create Iterators, and in the same way you're supposed to create a Paint once instead of creating them over and over in onDraw, you could reuse a single Rect object by setting its coordinates for each bar you need to draw.
Another approach you could try is creating a working Bitmap in your custom view, which you control, and calling drawBitmap inside onDraw to paint it onto the Canvas. That should be a pretty inexpensive call, and it can easily be stretched as required to fit the view.
The idea there, is that very time you get new data, you paint it onto the bitmap. Because of how your waveform looks (like blocks), and the fact you can scale it up, really all you need is a single vertical line of pixels for each value, right? So as the data comes in, you paint an extra line onto your already-existing bitmap, adding to the image. Instead of painting the entire waveform block by block every frame, you're just adding the new blocks.
The complication there is when you "fill" the bitmap - now you have to "shift" all the pixels to the left, dropping the oldest ones on the left side, so you can draw the new ones on the right. So you'll need a way to do that!
Another approach would be something similar to the circular buffer idea. If you don't know what that is, the idea is you take a normal buffer with a start and an end, but you treat one of the indices as your data's start point, wrap around to 0 when you hit the last index of the buffer, and stop when you hit the index you're calling your end point:
Partially filled buffer:
|start
123400
|end
Data: 1234
Full buffer:
|start
123456
|end
Data: 123456
After adding one more item:
|start
723456
|end
Data: 234567
See how once it's full, you shift the start and end one step "right", wrapping around if necessary? So you always have the most recent 6 values added. You just have to handle reading from the correct index ranges, from start -> lastIndex and then firstIndex -> end
You could do the same thing with a bitmap - start "filling" it from the left, increasing end so you can draw the next vertical line. Once it's full, start filling from the left by moving end there. When you actually draw the bitmap, instead of drawing the whole thing as-is (723456) you draw it in two parts (23456 then 7). Make sense? When you draw a bitmap to the canvas, there's a call that takes a source Rect and a destination one, so you can draw it in two chunks.
You could always redraw the bitmap from scratch each frame (clear it and draw the vertical lines), so you're basically redrawing your whole data buffer each time. Probably still faster than the drawRect approach for each value, but honestly not much easier than the "treat the bitmap as another circular buffer" method. If you're already managing one circular buffer, it's not much more work - since the buffer and the bitmap will have the same number of values (horizontal pixels in the bitmap's case) you can use the same start and end values for both
You would never do this with layouts. Layouts are for premade components. They're high level combinations of components and you don't want to dynamically add or remove views from it frequently. For this, you use a custom view with a canvas. Layouts aren't even an option for something like this.
I've been searching the internet for changing colour or overlaying a specific part of a bitmap. I've a square bitmap and I want to change the colour in a matrix pattern that is in equal 9 blocks which can be understood from the following image. (Cyan colour line here is for demonstration only)
I've read about boundary fill algorithm in College but here for java, I came to know that it is too bulky to perform such an operation for 9 specific parts. And I don't know how to use Paint with Canvas for such a square scenario.
So is there a method or something which can help me figure out how to paint a specific square by providing the size or location without performing a huge task on UI.
Here's what I need to achieve:
I can change the color, location,size by myself if there's something which can help me out.
Also, as there is white background, is there a way to not paint the background or do I have to use PNG?
Update:
I'm successfully able to divide the image in 9 equal parts using following code but PorterDuffColorFilter is not working as expected.
Code:
public void splitBitmap(Bitmap bitmap) {
int width, height;
// Divide the original bitmap width by the desired vertical column count
width = bitmap.getWidth() / 3;
// Divide the original bitmap height by the desired horizontal row count
height = bitmap.getHeight() / 3;
// Loop the array and create bitmaps for each coordinate
for (int x = 0; x < 3; ++x) {
for (int y = 0; y < 3; ++y) {
// Create the sliced bitmap
smallimages.add(Bitmap.createBitmap(bitmap, x * width, y * height, width, height));
}
}
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
The above code provides 9 bitmaps which then set to a GridLayout. But No mode of PorterDuffColorFilter is useful. Either the images remain original or is painted completely. I've tried every one of the modes available and none worked.
I've done something similar to this so after changing my code a bit, I think this is what you want:
Assuming that you don't have PNG,
First, remove the white background from your image Source:
Setting the white color as Transparent, you can use any color you want.
private static final int[] FROM_COLOR = new int[]{255, 255, 255};
private static final int THRESHOLD = 3;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.test_colors);
ImageView iv = (ImageView) findViewById(R.id.img);
Drawable d = getResources().getDrawable(RES);
iv.setImageDrawable(adjust(d));
}
private Drawable adjust(Drawable d)
{
int to = Color.TRANSPARENT;
//Need to copy to ensure that the bitmap is mutable.
Bitmap src = ((BitmapDrawable) d).getBitmap();
Bitmap bitmap = src.copy(Bitmap.Config.ARGB_8888, true);
for(int x = 0;x < bitmap.getWidth();x++)
for(int y = 0;y < bitmap.getHeight();y++)
if(match(bitmap.getPixel(x, y)))
bitmap.setPixel(x, y, to);
return new BitmapDrawable(bitmap);
}
private boolean match(int pixel)
{
//There may be a better way to match, but I wanted to do a comparison ignoring
//transparency, so I couldn't just do a direct integer compare.
return Math.abs(Color.red(pixel) - FROM_COLOR[0]) < THRESHOLD &&
Math.abs(Color.green(pixel) - FROM_COLOR[1]) < THRESHOLD &&
Math.abs(Color.blue(pixel) - FROM_COLOR[2]) < THRESHOLD;
}
Above code will change the color to transparent and below code will split the bitmap into 9 same size bitmaps:
public void splitBitmap(Bitmap bitmap) {
ArrayList<Bitmap> smallimages = new ArrayList<>(9);
int width, height;
// Divide the original bitmap width by the desired vertical column count
width = bitmap.getWidth() / 3;
// Divide the original bitmap height by the desired horizontal row count
height = bitmap.getHeight() / 3;
// Loop the array and create bitmaps for each coordinate
for (int x = 0; x < 3; ++x) {
for (int y = 0; y < 3; ++y) {
// Create the sliced bitmap
smallimages.add(Bitmap.createBitmap(bitmap, x * width, y * height, width, height));
}
}
}
At last, you can use PorterDuffColorFilter on every bitmap:
imageView.setImageDrawable(arrayList.get(0));
imageView.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_ATOP);
There can be problems as it works for me and might not for you but this is the way you can achieve your needed result.
If any problem persists, I can help.
I am working on a project in Android that builds Abelian Sandpiles (a type of 2D cellular Automaton). I have a grid of cells (that starts out small but later grows) and I'm drawing square (or circles) on the grid to show the state of each cell. At each step, I update usually less than 30% of the cells.
My basic approach is taken from this post: Android: How to get a custom view to redraw partially?
I draw all the shapes to the canvas and cache it as a bitmap, then at each step I update only the cells that need updating, then cache the result and repeat. This works well enough when the grid is fairly small (less than 50 x 50), but becomes increasingly unsatisfactory as the grid size increase. The problems are that
1) it is too slow when the number of updates becomes high
2) even when it runs smoothly, the drawing doesn't look clean - e.g., a line of small rectangles looks quite choppy and inconsistent (see image).
I am sure that there must be a better way to approach this problem. With a larger grid (e.g., 150 x 150), I'm drawing rects or circles with a width of ~2.33, and this can't be optimal. Any advice for improving performance and/or image quality?
Simplified drawing code is here:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (ready) {
if (needsCompleteRedraw) {
this.cachedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
cacheCanvas = new Canvas(this.cachedBitmap);
doInitialDrawing(cacheCanvas);
canvas.drawBitmap(this.cachedBitmap, 0, 0, null);
needsCompleteRedraw = false;
} else {
canvas.drawBitmap(this.cachedBitmap, 0, 0, null);
doPartialRedraws(cacheCanvas);
}
}
}
private void doInitialDrawing(Canvas clean) {
for (int i = 0; i < pile.gridHeight; i++) {
for (int j = 0; j < pile.gridWidth; j++) {
int state = pile.getGridValueAtPoint(new Point(j, i ));
clean.drawRect(j * cellSize, i * cellSize, (j + 1 )* cellSize, (i + 1) * cellSize, paints[state]);
}
}
}
private void doPartialRedraws(Canvas cached) {
for (Point p : pile.needsUpdateSet) {
int state = pile.getGridValueAtPoint(p);
cached.drawRect(p.x * cellSize, p.y * cellSize, (p.x + 1 )* cellSize, (p.y + 1) * cellSize, paints[state]);
}
pile.needsUpdateSet = new HashSet<Point>();
}
and my paint objects are set with antialias as true, and I've tried setting the style to both FILL and FILL_AND_STROKE.
Any suggestions would be much appreciated.
Ok. Several problems here.
1)Don't ever create a canvas in onDraw. If you think you need to, you haven't architected your drawing code correctly.
2)The point of doing draws to a cache bitmap is NOT to draw the cache in onDraw, but to do it on its own thread- or at least not at draw time.
You should have a second thread that draws to the cached bitmap on demand, then calls postInvalidate() on the view. The onDraw function should only be a call to drawBitmap, drawing the cached bitmap to the screen.
I am developing an app which draws more or less a two-dimensional matrix of values to a canvas. The values of this matrix are scaled to Alpha levels to illustrate intensity, and the coordinates for the matrix are simply extrapolated from row and column indexes. Below is my onDraw routine.
public void onDraw(Canvas canvas, float [][] spectrum, float nsegs,int seglen) {
//canvas.translate(0,0);
//alpha = 0;
int canHeight = canvas.getHeight();
int canWidth = canvas.getWidth();
//float[] array = generateData(512);
float [] spec = new float[seglen];
final float bw = (float)(canWidth-2)/nsegs;
final float bh = (float)(canHeight-2)/(float) seglen;
for (int i = 0;i<seglen;i++){
spec[i] = spectrum[i][index]; // One column at a time
}
float max = maxVal(spec);
float min = minVal(spec);
xcoor = index;
for (int n = 0; n < seglen; n++){
//Scale value to alpha (0-255)
alpha =(int)Math.round((((spec[n] - min)/max)*255.0));
ycoor = n;
paint.setAlpha(alpha);
canvas.drawPoint(xcoor,ycoor, paint);
}
index = (int) (index +1);
if (index == nsegs-1){
index = 0;
}
}
Here paint configuration is pre-defined as:
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(2);
This program draws one pixel at a time, fills one column of pixels equal to the number of elements in a column of the matrix. Then it starts on the next column, where the previous column is still displayed and so forth. At this stage when all columns are full it stars from the first column again, drawing on top of previous elements.
The Problem: The tailing columns although already drawn appear to flicker and jump around, as does the Alpha. I have attempted to canvas.save() and canvas.restore() to capture the entire canvas and restore it after a column is printed. I have double checked all my row and column indexing and alpha vales to ensure the coordinates increment as per desired (and they do). This is very similar to the sample APIdemo DrawPoints.java, however there are three primary differences.
I am using DrawPoint not DrawPoints, and
I don't use "canvas.setColour" as it removes the tailing columns from the canvas.
This onDraw function is operating in a Thread which extends SurfaceView
Any idea's would be much appreciated, thank you for your time.
In the case above, I was using a SurfaceView instead of a View. Out of the Android dev docs
Note: On each pass you retrieve the Canvas from the SurfaceHolder, the previous state of the Canvas will be retained. In order to properly animate your graphics, you must re-paint the entire surface. For example, you can clear the previous state of the Canvas by filling in a color with drawColor() or setting a background image with drawBitmap(). Otherwise, you will see traces of the drawings you previously performed.
The Solution, re-draw the entire canvas each time to prevent the jitter.