1

Hi I am working on plotting a real time graph of incoming signals using SurfaceView. The sampling rate is 128Hz and the target graph refresh rate is 50Zh.

Things run pretty smoothly, the points are drawn real-time properly.

I plot the data in segments of a few points using Path() for each segment I call path.computeBounds() to get a rect that I will use to call holder.lockCanvas(rect) and draw the path. Using a rect prevents flickering and reduces cpu usage

when the graph reaches the end I lock the entire canvas and clear the background, draw the graph frame and then continue on plotting.

the problem is that at the beginning of each new "page" I get a ghost image from the last page:

ghost image

I believe this is caused by double buffering / use of a dirty area when plotting.

I have looked for solutions to this problem but none seem adequate for this type of application. Any help is most welcome.

Thanks Jean-Pierre

Code follows:

    private void draw() {
    Point point = null;
    Canvas canvas = null;
    Path path = new Path();
    ArrayList<Point> pointArray;
    float oldX = -1;
    boolean setToClear = false;
    boolean isNewSegment = false;

    if (samplesInQueue == 0) {
        return;
    }

    pointArray = new ArrayList<Point>((int) samplesInQueue);

    for (int i = 0; i < samplesInQueue; i++) {
        // take a peek at the point without retrieving it from the point
        // queue
        point = Points.peek();
        // check if first point of segment is the start of a page
        if (i == 0) {
            if (lastSegmentEndPoint != null) {
                if (point.x < lastSegmentEndPoint.x) {
                    // yes then we will need to clear the screen now
                    isNewSegment = true;
                }
            } else {
                // yes then we will need to clear the screen now
                isNewSegment = true;
            }
        }

        if (point != null) {
            if (point.x > oldX) {
                // put consecutive points in the path point array
                point = Points.poll();
                samplesInQueue--;
                pointArray.add(point);
                oldX = point.x;
            } else {
                // we have a wrap around, stop and indicate we need to clear
                // the screen on the next pass
                if (!isNewSegment) {
                    setToClear = true;
                }
                break;
            }
        }
    }

    // no points, return
    if (pointArray.size() == 0) {
        return;
    }

    // fill the path
    for (int i = 0; i < pointArray.size(); i++) {
        Point p = pointArray.get(i);

        if (i == 0) {
            if (lastSegmentEndPoint != null) {
                if (p.x >= lastSegmentEndPoint.x) {
                    // if we have the end of the last segment, move to it
                    // and line to the new point
                    path.moveTo(lastSegmentEndPoint.x, lastSegmentEndPoint.y);
                    path.lineTo(p.x, p.y);
                } else {
                    // otherwise just line to the new point
                    path.moveTo(p.x, p.y);
                }
            } else {
                path.moveTo(p.x, p.y);
            }
        } else {
            path.lineTo(p.x, p.y);
        }
    }

    if (clear || isNewSegment) {
        if (clear) {
            clear = false;
        }
        // we need to clear, lock the whole canvas
        canvas = holder.lockCanvas();
        // draw the graph frame / scales
        drawGraphFrame = true;
        drawGraphFrame(canvas);
    } else {
        // just draw the path
        RectF bounds = new RectF();
        Rect dirty = new Rect();
        // calculate path bounds
        path.computeBounds(bounds, true);

        int extra = 0;
        dirty.left = (int) java.lang.Math.floor(bounds.left - extra);
        dirty.top = (int) java.lang.Math.floor(bounds.top - extra);
        dirty.right = (int) java.lang.Math.round(bounds.right + 0.5);
        dirty.bottom = (int) java.lang.Math.round(bounds.bottom + 0.5);

        // just lock what is needed to plot the path
        canvas = holder.lockCanvas(dirty);
    }

    // draw the path
    canvas.drawPath(path, linePaint);

    // unlock the canvas
    holder.unlockCanvasAndPost(canvas);

    // remember last segment end point
    lastSegmentEndPoint = pointArray.get(pointArray.size() - 1);

    // set clear flag for next pass
    if (setToClear) {
        clear = true;
    }
}

Draw frame / clear graph code

    private void drawGraphFrame(Canvas canvas) {

    if (!drawGraphFrame) {
        return;
    }

    if (canvas == null) {
        Log.e(TAG, "trying to draw on a null canvas");
        return;
    }

    drawGraphFrame = false;

    // clear the graph
    canvas.drawColor(Color.BLACK, Mode.CLEAR);

    // draw the graph frame
    canvas.drawLine(leftMargin, topMargin, leftMargin, mCanvasHeight - bottomMargin, framePaint);
    canvas.drawLine(leftMargin, mCanvasHeight - bottomMargin, mCanvasWidth - rightMargin, mCanvasHeight
            - bottomMargin, framePaint);

    // more drawing
}

3 Answers3

0

Your problem is quite straight forward.. your only locking the new portion of the canvas that the new path covers. So the best thing to do is to make your path and dirty rect's private members of your class. Then at the start of your draw method get the path's current bounds (the old bounds) in your dirty rect. Now call path.rewind(); and start modifying your path. After do a union on the dirty rect with the new bounds. Now your dirty rect covers the old and new rect's. So your clear will remove the old path. This also reduces overhead because you don't want to be allocating 100+ objects per second for rect's and path's. Now since your drawing an oscilloscope then you probably want to adjust the old bounds to only be a portion of the width of the view. The same amount your new portion covers.

Hope that's cleared things up.

Simon
  • 10,932
  • 50
  • 49
  • Thanks Simon. This does not really work in this case. the rewind() causes the path to grow since the old points are kept. this causes the performance to drop fast after a few hundred points. This is why I only draw a few points at a time. Also, In your last statement (oscilloscope), I believe this is what ends being done in my code, the rect only covers the required area. Maybe I'm not quite understanding what you mean. – Jean-Pierre Semery May 15 '14 at 13:08
0

My simple answer is just using this function clear_holder() wherever you want to clear the canvas. I copy and paste 3 line for 3 times because it need 3 times clear to leave holder blank.

After clearing holder, you should draw any new thing you want!

This link give me this source code!

private void clear_holder(SurfaceHolder holder){
        Canvas c = holder.lockCanvas();
        c.drawColor( 0, PorterDuff.Mode.CLEAR );
        holder.unlockCanvasAndPost(c);
        c = holder.lockCanvas();
        c.drawColor( 0, PorterDuff.Mode.CLEAR );
        holder.unlockCanvasAndPost(c);
        c = holder.lockCanvas();
        c.drawColor( 0, PorterDuff.Mode.CLEAR );
        holder.unlockCanvasAndPost(c);
    }
0

It looks like you are clearing the canvas so, it's not double buffering problem. I think it's related to your path been reused.

Try adding adding the next line when starting new page.

path.reset();
Ilya Gazman
  • 31,250
  • 24
  • 137
  • 216