Canvas is not Showing Image/Drawable - android

I am doing a work on SVG Paths and Image. I have loaded SVG file and get an image and try to set thi image on canvas . But canvas is not showing image. I check the height and width and null check of this image/picture and it is not null so i am unable to understand that why canvas is not showing image. any help
My code:
public class MainActivity extends Activity{
Context c;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
c=getApplicationContext();
setContentView(new GameView(this));
}
public class GameView extends View{
private int width, height;
private long svgId;
Picture picture;
long startTime;
float scaleFactor;
public GameView(Context context) {
super(context);
SVG svg = SVGParser.getSVGFromResource(getResources(),R.raw.android);
picture = svg.getPicture();
}
#Override
protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
// get visible area
width = right - left;
height = bottom - top;
}
#Override
public void onDraw(Canvas canvas) {
// paint a white background...
canvas.drawColor(Color.BLACK);
if (canvas!=null)
{
Toast.makeText(c, "yahooooooooooooooooo"+picture.getHeight(), Toast.LENGTH_LONG).show();
scaleFactor=Math.min((float)getHeight()/picture.getHeight(),(float)getWidth()/picture.getWidth());
canvas.scale((float)scaleFactor,(float)scaleFactor);
canvas.drawPicture(picture);
}
}
}
}

There is a known issue with svg-android that with certain images, the hardware acceleration can actually cause the image to not show up. I'm not sure of the exact circumstances of what causes this, but I do know it happened to me, as I documented in this question. The key is to disable hardware acceleration for the view. Depending on what your target it, you might not need to do something quite as fancy as I did (Android 3.0+ doesn't need to check the build version), but here's the key part. Add the code I included in the constructor to your constructor, then add the disableHardwareAcceleration bit
public GameView(Context context,AttributeSet attrs) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
disableHardwareAcceleration();
}
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void disableHardwareAcceleration()
{
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

This is what I use to return a drawable object that I will use through all my app
It works quite well.
final BitmapDrawable getSVG_Drawable
(int svgResourceID, int width, int height)
{
// Get a Picture from the SVG in res/raw.
final SVG vector =
SVGParser.getSVGFromResource(getResources(), svgResourceID);
final DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
// Redraw the picture to a new size.
final Bitmap bmp =
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas cnv = new Canvas(bmp);
cnv.setDensity((int) (metrics.xdpi));
cnv.drawPicture(vector.getPicture(), new Rect(0, 0, width, height));
final BitmapDrawable drw = new BitmapDrawable(getResources(), bmp);
// Return the drawable.
return drw;
}

Related

Draw on canvas without making previously drawn bitmap cleared

I need to draw bitmap on canvas and then start drawing over it. When I draw other objects on canvas the previously drawn bitmap gets cleared. To avoid the bitmap from getting cleared, I have to draw it in each ondraw() call. There must be some other way to just update over the previously drawn drawing, else it would be really efficient as I may have to draw many bitmaps.
#Override
protected void onDraw(Canvas mCanvas) {
for (Pair<Path, Paint> p : paths) {
mCanvas.drawPath(p.first, p.second);
}
if(merge){
canvas.drawBitmap(bmp, transform, new Paint());
}
}
So, what would be the most efficient way to draw over previously drawn drawing without losing it.
I tried writing this by implementing the chess pieces using BitmapDrawable, and then only call invalidateDrawable on the piece that was moved.
That worked fine, but when I checked which parts are redrawn, it turned out that everything seems to be redrawn (or at least all the onDraw functions are being called).
Checking the documentation of the invalidate method on the View class again, I found the following:
This method was deprecated in API level 28.
The switch to hardware accelerated rendering in API 14 reduced the importance of the dirty rectangle. In API 21 the given rectangle is ignored entirely in favor of an internally-calculated area instead. Because of this, clients are encouraged to just call invalidate().
This suggests to me that maybe there is simply no need to worry about this case. It seems that Android internally figures out what to redraw, and efficiently does so.
Just in case it may help, you can find the working version on my GitHub.
Additionally, here is the most relevant part of the App:
public class RedrawTest extends View {
private class ChessPiece extends BitmapDrawable {
final int id;
public ChessPiece(Context context, int _id) {
super(getResources(), getBitmapFromAsset(context, "chess_piece.png"));
id = _id;
}
}
final Bitmap chessboard;
final ChessPiece[] chessPieces;
final Rect boardSize;
final Paint paint = new Paint();
final ViewOverlay overlay;
final int borderSize = 32;
final int nChestPieces = 6;
private static Bitmap getBitmapFromAsset(Context context, String filePath) {
AssetManager assetManager = context.getAssets();
InputStream inputStream;
Bitmap bitmap = null;
try {
inputStream = assetManager.open(filePath);
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (IOException e) {
// handle exception
}
return bitmap;
}
public RedrawTest(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
chessboard = getBitmapFromAsset(context, "chessboard.jpg");
boardSize = new Rect(
borderSize, borderSize,
chessboard.getWidth()-borderSize, chessboard.getHeight()-borderSize
);
overlay = getOverlay();
chessPieces = new ChessPiece[nChestPieces];
for (int i=0; i<nChestPieces; ++i) {
chessPieces[i] = new ChessPiece(context, i);
chessPieces[i].setBounds(getBoardRect(0, i));
overlay.add(chessPieces[i]);
}
}
public void redraw(int i, int j, int pieceId) {
chessPieces[pieceId].setBounds(getBoardRect(i, j));
invalidateDrawable(chessPieces[pieceId]);
}
private Rect getBoardRect(int i, int j) {
return new Rect(
boardSize.left + j*boardSize.width()/8 + 5,
boardSize.top + i*boardSize.height()/8 + 5,
boardSize.left + (j+1)*boardSize.width()/8 - 5,
boardSize.top + (i+1)*boardSize.height()/8 - 5);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(this.chessboard, 0, 0, paint);
}
}
I found that not using a custom view class and the onDraw method makes it so the canvas doesn't clear and hence I had to manually clear it myself for my purposes.
See below for the code, just remove the canvas.drawColor line as that is where I paint the canvas clear again.
public void doCanvas(){
//Create our resources
Bitmap bitmap = Bitmap.createBitmap(mLittleChef.getWidth(), mLittleChef.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
final Bitmap chefBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.dish_special);
final Bitmap starBitmap= BitmapFactory.decodeResource(getResources(),R.drawable.star);
//Link the canvas to our ImageView
mLittleChef.setImageBitmap(bitmap);
ValueAnimator animation= ValueAnimator.ofInt(canvas.getWidth(),0,canvas.getWidth());
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (Integer) animation.getAnimatedValue();
//Clear the canvas
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.drawBitmap(chefBitmap, 0, 0, null);
canvas.save();
canvas.translate(value,0);
canvas.drawBitmap(starBitmap, 0, 0, null);
canvas.restore();
//Need to manually call invalidate to redraw the view
mLittleChef.invalidate();
}
});
animation.addListener(new AnimatorListenerAdapter(){
#Override
public void onAnimationEnd(Animator animation) {
simpleLock= false;
}
});
animation.setInterpolator(new LinearInterpolator());
animation.setDuration(mShortAnimationDuration);
animation.start();
}
In pixi.js there is options.clearBeforeRender (https://pixijs.download/dev/docs/PIXI.CanvasRenderer.html) and pixi.js probably using https://www.w3schools.com/tags/canvas_clearrect.asp .
Your question already been asked (but 7y ago, so maybe outdated), and they did as you do How to save objects previously drawn to the Canvas on a redraw? .
You may want to profile, to make sure it is a performance issue, maybe the way you do it doesn't look efficient, but is efficient, if you see what I mean.

Custom view's onDraw receives canvas with different sizes

I have a class derived from ImageView:
public class TouchView extends ImageView
{
#Override
protected void onDraw(Canvas canvas)
...
The touchview is created only once in the activity's onCreate and populated with a drawable from a SVG file.
ImageView imageView = new TouchView(this);
imageView.setScaleType(ImageView.ScaleType.MATRIX);
FrameLayout f = (FrameLayout)findViewById(R.id.frame2);
FrameLayout.LayoutParams l = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.FILL_PARENT, FrameLayout.LayoutParams.FILL_PARENT);
f.addView(imageView, l);
...
is = openFileInput(svgname);
svg = SVGParser.getSVGFromInputStream(is);
is.close();
Drawable d = svg.createPictureDrawable();
imageView.setImageDrawable(d);
All the environment remains the same all the time. Yet in the onDraw method I'm getting canvas with different sizes between the events. That is the code:
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Log.v("DRAW", " w= " + canvas.getWidth() + " h=" + canvas.getHeight());
...
}
produces logs with lines, where width and height of the canvas change back and forth from normal 1024*728 (this is the correct view dimension on the tablet) to 200*160 (strange thing introducing bugs in my drawings). I'm embarrassed.
Should the canvas be always of the same size for the same view/drawable? The documentation says that getWidth and getHeight methods return dimensions of the current drawing layer, but it's not clear what is the layer, how many of them is created for the canvas "behind the scene" and how to control this process.
I'd appreciate any explanation on how to get consistent drawing behaviuor, specifically by getting actual size of the view being painted in onDraw.
Currently I'm using a workround with a call to the view's getDrawingRect, but I'm not sure it is a proper way, because it seems that the canvas parameter of onDraw should be all-sufficient for drawing sizing.
I had the same issue, here is how I got this fixed, hope it helps you
protected void onDraw(Canvas c) {
super.onDraw(c);
int w = getWidth(), h = getHeight();
// resize
Matrix resize = new Matrix();
resize.postScale((float)Math.min(w, h) / (float)mMarker.getWidth(), (float)Math.min(w, h) / (float)mMarker.getHeight());
imageScaled = Bitmap.createBitmap(mMarker, 0, 0, mMarker.getWidth(), mMarker.getHeight(), resize, false);
c.drawBitmap(imageScaled, 0,0, paint);
}
Where mMarker is defined in the custom ImageView constructor.
...
private Bitmap mMarker, imageScaled;
Paint paint = new Paint();
//Java constructor
public AvatarImageView(Context context) {
super(context);
init();
}
//XML constructor
public AvatarImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// load the image only once
mMarker = BitmapFactory.decodeResource(getResources(), R.drawable.silhouette_48);
mMarker.setHasAlpha(true);paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(4);
//invalidate(); // don't know if I need this
}

How to use custom views, e.g. setting drawable

I think I'm a bit confused about how to use custom views. I'm following along with slides from a talk given by Eric Burke from Square (from this year's anddevcon, slides here: http://www.andevcon.com/AndevCon_II/downloadpresentation.aspx?aid=Taming_Android__User_Experience_Lessons_from_Square_pdf.zip&sid=2).
His code, or at least the part he showed in the slides, went something like this:
public class EditablePhoto extends View {
private Bitmap framedPhoto;
private Bitmap image;
private Drawable placeholder;
public EditablePhoto(Context context) {
super(context);
}
#Override protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
int measuredWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
int measuredHeight = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
//ensure view always square
int min = Math.min(measuredHeight, measuredWidth);
setMeasuredDimension(min, min);
}
#Override
protected void onDraw(Canvas canvas) {
if(placeholder == null && image==null) return;
if(framedPhoto == null) {
createFramedPhoto(Math.min(getWidth(), getHeight()));
}
canvas.drawBitmap(framedPhoto, 0, 0, null);
}
private void createFramedPhoto(int size) {
Drawable imageDrawable = (image!=null)
? new BitmapDrawable(image) : placeholder;
Bitmap output = Bitmap.createBitmap(size, size,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
RectF outerRect = new RectF(0, 0, size, size);
float outerRadius = size / 18f;
//Red rectangle
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
canvas.drawRoundRect(outerRect, outerRadius, outerRadius, paint);
paint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.SRC_IN));
imageDrawable.setBounds(0, 0, size, size);
canvas.saveLayer(outerRect, paint, Canvas.ALL_SAVE_FLAG);
imageDrawable.draw(canvas);
canvas.restore();
}
}
What I don't get is how to actually use this View now.... Where and when do you set the bitmaps, which are private fields in this class...?
Generally confused and would love some enlightenment.
More than one year passed, but I hope this will help anyone who looking for the right answer. In my case, I putted this line of code
framedPhoto = output;
as the last one in createFramedPhoto() method. It works.
In the example, the author created a rounded rectangle as background then he draw the bitmap on it with XOR mode, so all pixel outside the rounded rectangle will be trim off.
OnDraw() is the method where you will Draw your view on canvas. here too you can analyze onDraw() will fisrt call CreateFramePhoto then draw this Bitmap on canvas .
You can add this customView in layout Either from xml or in Java Class
1) Through Xml :
<EditablePhoto android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
..........................
/>
dont forgate to add constructor EditablePhoto(Context context, AttributeSet attributeSet) for this case
2) through Java class :
EditablePhoto editablePhoto = new EditablePhoto(this);
addView(editablePhoto) // or do anthing you want with this

Make bitmap drawable tile in x but stretch in y

I have an image which I'm using as a background for a RelativeLayout. The image needs to be tiled horizontally to make a pattern.
I'm able to get the image to tile horizontally by using this code:
BitmapDrawable b3 = (BitmapDrawable)getResources().getDrawable(R.drawable.background);
b3.setTileModeX(Shader.TileMode.REPEAT);
v.findViewById(R.id.layout).setBackgroundDrawable(b3);
The problem is that the image also tiles vertically. It seems to tile in the "clamp" mode in the vertical, but "repeat" mode in the horizontal. Here is a screenshot:
As you can see, the image is just a little bit smaller than the space it occupies, and the bottom edge is "clamped".
How can I set the image to stretch vertically but tile horizontally?
This method invokes creation of new bitmap but it looks like it's acrhiving your goal
View view = findViewById(R.id.layout);
BitmapDrawable bd = (BitmapDrawable) getResources().getDrawable(R.drawable.tile);
int width = view.getWidth();
int intrinsicHeight = bd.getIntrinsicHeight();
Rect bounds = new Rect(0,0,width,intrinsicHeight);
bd.setTileModeX(TileMode.REPEAT);
bd.setBounds(bounds);
Bitmap bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), bd.getBitmap().getConfig());
Canvas canvas = new Canvas(bitmap);
bd.draw(canvas);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
view.setBackground(bitmapDrawable);
Please note that it only works if view was already layouted, so a method lile onWindowFocusChanged is a good place for this code.
I feel it is straight forward:(This code will tile in Y and repeat in x)
In your onWindowFoucsChanged you call:
public void onWindowFocusChanged(boolean hasFocus) {
// TODO Auto-generated method stub
super.onWindowFocusChanged(hasFocus);
Drawable d = getRepeatingBG(this, R.drawable.image_that_you_want_to_repeat);
body_view.setBackgroundDrawable(d);
}
private Drawable getRepeatingBG(Activity activity, int center)
{
DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled=true;
Bitmap center_bmp = BitmapFactory.decodeResource(activity.getResources(), center, options);
center_bmp.setDensity(Bitmap.DENSITY_NONE);
center_bmp=Bitmap.createScaledBitmap(center_bmp, dm.widthPixels , center_bmp.getHeight(), true);
BitmapDrawable center_drawable = new BitmapDrawable(activity.getResources(),center_bmp);
//change here setTileModeY to setTileModeX if you want to repear in X
center_drawable.setTileModeY(Shader.TileMode.REPEAT);
return center_drawable;
}
Far too late for you, but may be useful for others.
You can create a custom View in order to do this. Simply scale your source bitmap to be as high as your view and then draw it repeatedly on the canvas:
public class RepeatingXImageView extends View {
Bitmap bitmap;
Paint paint;
public RepeatingXImageView(Context context) {
super(context);
}
public RepeatingXImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RepeatingXImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if(changed) {
paint = new Paint();
bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.seekbar_overlay);
bitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth(), bottom - top, false);
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(bitmap == null) return;
int left = 0;
while(left < getWidth()) {
canvas.drawBitmap(bitmap, left, 0, paint);
left += bitmap.getWidth();
}
}
}
Having the same problem myself. Tried using 9patch image but it seems you cannot use 9patch images along with tiling, the 9patch stretches the image no matter what you define in the tiling property.
At the end what i will do is ask the creator of the image to stretch it vertically or do it myself. I would love a better solution if anyone finds one.

How can I draw a 'shadow' version of an image?

I'm stuck on a simple problem which is driving me nuts. In the standard Android MapView overlay images have a shadow drawn for them automatically when you call the drawAt method. I want to recreate the same shadow effect, but I'm not sure how to make the shadow version of the image (which is drawn separately from the main image) align properly with the main image.
private static class SampleView extends View {
private Drawable mDrawable;
private int mMarkerXOffset;
private int mMarkerYOffset;
public SampleView(Context context) {
super(context);
mDrawable = context.getResources().getDrawable(R.drawable.icon);
mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
mMarkerXOffset = (mDrawable.getIntrinsicWidth() / 2);
mMarkerYOffset = mDrawable.getIntrinsicHeight();
}
private void DrawNormalImg(Canvas canvas, int nX, int nY) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(nX, nY);
mDrawable.draw(canvas);
canvas.restore();
}
private void DrawShadowImg(Canvas canvas, int nX, int nY) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
mDrawable.setColorFilter(0x7f000000, PorterDuff.Mode.SRC_IN);
canvas.translate(nX,nY);
canvas.skew(-0.9F, 0.0F);
canvas.scale(1.0F, 0.5F);
mDrawable.draw(canvas);
mDrawable.clearColorFilter();
canvas.restore();
}
#Override protected void onDraw(Canvas canvas) {
int nX = 100;
int nY = 50;
canvas.drawColor(Color.WHITE);
DrawShadowImg(canvas, nX, nY);
DrawNormalImg(canvas, nX, nY);
}
Nice question! I think the solution will be skew the image as a parallelogram. Consider an image of a map pin that looks something like this:
..X..
.XXX.
..X..
..|..
..|..
..|..
Where . is transparency. You would want to skew the image like this for the shadow:
..X..
.XXX.
..X..
..|..
..|..
..|..
Notice the top and bottom are the same width as in the original image.

Categories

Resources