Scaling Canvas to resize SVG in Android - android

I'm not having much luck wrapping my head around this, so I'm hoping someone can help me out.
I'm getting a drawable from an SVG using svg-android, but the drawable isn't scaling to the view. Everything that I've been able to find says I should draw directly to canvas and rescale the canvas, but when I try that it only seems to change the bounds but not scale the image.
This is what I've tried so far:
ImageView testview = (ImageView)findViewById(R.id.testview);
//Get SVG and convert to drawable
SVG vector = SVGParser.getSVGFromResource(getResources(),R.drawable.testvector);
Drawable test = vector.createPictureDrawable();
testview.setBackground(test); //displays fine, but won't scale to the dimensions of
//the View
//function that clips the image but doesn't scale:
Drawable testTwo = new CustomPictureDrawable(vector.getPicture(),
(float)0.5, (float)0.5);
testView.setBackground(testTwo);
class CustomPictureDrawable extends PictureDrawable {
private float scalex, scaley;
public CustomPictureDrawable(Picture picture, float scalex, float scaley) {
super(picture);
this.scalex = scalex;
this.scaley = scaley;
}
#Override
public void draw(Canvas canvas) {
Matrix original = canvas.getMatrix();
canvas.scale(scalex, scaley);
super.draw(canvas);
canvas.setMatrix(original);
}
}
//doesn't display anything
Picture testThree = vector.getPicture();
Bitmap b = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_4444);
Canvas c = new Canvas(b);
c.drawPicture(testThree, new Rect(0,0,10,10));
testview.draw(c);
I've also found a function that will create a scaled bitmap, but the image quality is significantly reduced, so I may as well just use scaled PNGs.
Obviously I'm missing something, and my lack of experience is making it really frustrating.
What I'd like to be able to do is have svg-android completely re-scale the SVG before pulling a Picture or PictureDrawable out of it, but I can't figure out how to step through the SVGParser, and running multipliers on every coordinate pair would probably be super resource intensive anyway.
[edit] Is the only way to scale and re-draw the Picture and assign that to a view to create custom Views and override OnDraw?
i.e.
Picture testThree = vector.getPicture();
Bitmap b = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_4444);
Canvas c = new Canvas(b);
c.drawPicture(testThree, new Rect(0,0,10,10));
//CustomView extends ImageView or Button or whatever with OnDraw overridden and no
//other changes
CustomView testview = (CustomView)findViewById(R.id.testview);
testview.OnDraw(c);
Am I on the right track? Canvas c would overwrite the default canvas (which is what I want), wouldn't it?

I figured it out. Or at least figured out a method that works. The answer was staring me in the face in that scaled bitmap function that I didn't like. I fundamentally misunderstood how the Picture class and Draw calls work.
Code that seems to have pulled it off:
//Get a Picture from the SVG
SVG vector = SVGParser.getSVGfromResource(getResources(), R.raw.testvector);
Picture test = vector.getPicture();
//Redraw the picture to a new size
Bitmap bitmap = Bitmap.createBitmap(desired width, desired height, config);
Canvas canvas = new Canvas(bitmap);
Picture resizePicture = new Picture();
canvas = resizePicture.beginRecording(desiredWidth, desiredGeight);
canvas.drawPicture(test, new Rect(0,0,desiredWidth, desiredHeight);
resizePicture.endRecording();
//get a drawable from resizePicture
Drawable vectorDrawing = new PictureDrawable(resizePicture);
I can size it to whatever View I want by getting desiredWidth and desiredHeight from getWidth() and getHeight() calls, and setBackground(vectorDrawing) at the end to put it on the View.

I had a similar problem and this is what I learned and how I solved it.
First I tried to render my svg directly into a canvas
svg.renderToCanvas(canv);
which didn't have the size I wanted. Then I tried to resize the image using
svg.renderToCanvas(canv,new RectF(0,0,200,200));
and
svg.setDocumentWidth(200);
svg.renderToCanvas(canv);
but both didn't work, so I started to look at my SVG. The problem was that my SVG started like this
<svg width="66.3679625" height="100.4148375" xmlns="http://www.w3.org/2000/svg">
and it seems impossible, once width and height are set in the svg, to change this in code afterwards. There is also an answer in the AndroidSvgFAQ where they recommend to remove those attributes. So I changed this to
<svg version="1.1" viewBox="0 0 1280 696" xmlns="http://www.w3.org/2000/svg">
The viewBox attribute is the frame of my SVG picture, so if now I use the code above
svg.setDocumentWidth(200);
svg.renderToCanvas(canv);
I get the content of the box with upper left angle at (0,0) and lower right angle at (1200,696) resized such that the width is 200. You can change the behaviour for the ratio initialy it keeps ratios. Note that one can omit width, heigth and ViewBox in SVG and set the ViewBox programmatically
svg.setDocumentViewBox(0,0,width,height);
To get to know more about the ViewBox attribute see W3ViewBox.

Related

Android canvas positioning, and customization

So this is my scenario:
I have an svg image that contains all the music notes on the staff (sort of sprite sheet)
I have two svg image that contains the key (maybe i can merge them together anyway)
All of them are converted to android Vector Drawable.
What i want to do is to select the note from the first svg, select the cleff, and then show them next to each other (the two images should be aligned).
So what i managed to achieve is to select the portion of svg for the note (i need to refine the rect size). But what i'm still having problem is to show both of them on the same line.
public MusicScore(Context context) {
super(context);
paint = new Paint();
paint.setColor(Color.BLACK);
Resources res = context.getResources();
musicClef = (VectorDrawable) res.getDrawable(R.drawable.ic_bassclef, null);
musicNotes = (VectorDrawable) res.getDrawable(R.drawable.ic_musicnotes, null);
int width = getWidth();
int height = getHeight();
Log.i("MUSIC", "H: " + musicClef.getMinimumHeight() + " W: " + musicClef.getMinimumWidth());
}
#Override
protected void onDraw(Canvas canvas){
Log.i("MUSIC", "Called");
int left = getWidth()/2;
int top = getHeight()/2;
musicClef.setBounds(0,0,musicClef.getIntrinsicWidth(), musicClef.getIntrinsicHeight() );
Bitmap source = Bitmap.createBitmap(musicNotes.getIntrinsicWidth(), musicNotes.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Bitmap clefSource = Bitmap.createBitmap(musicClef.getIntrinsicWidth(), musicClef.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas newcanvas = new Canvas(source);
Canvas clefCanvas = new Canvas(clefSource);
int notesLeft = musicClef.getIntrinsicWidth();
int notesTop = musicClef.getIntrinsicHeight();
musicNotes.setBounds(0, 0, musicNotes.getIntrinsicWidth(), musicNotes.getIntrinsicHeight());
musicNotes.draw(newcanvas);
musicClef.draw(clefCanvas);
Rect rect = new Rect(1150,0,1700, musicNotes.getIntrinsicHeight());
Rect rect2 = new Rect(notesLeft ,0, notesLeft + 450, musicNotes.getIntrinsicHeight());
Rect clefRect = new Rect(0, 0, musicClef.getIntrinsicWidth(), musicClef.getIntrinsicHeight());
canvas.drawBitmap(clefSource, null, clefRect, null);
canvas.drawBitmap(source, rect, rect2, null);
}
So with that code i can show a portion of the notes drawable, after converting it to a Bitmap. And i can also draw both of them, and the horizontal position is aligned. The problem is that the vertical position is not aligned, what i'm getting is:
So i know that my code is not correct (i'm pretty new to canvas in android and trying to figure out what to do), and what i learned so far is:
that in order to select a portion of the image i need to convert it to the Bitmap (i haven't found any way to do it directly with the VectorDrawable)
While drawing a Bitmap on the canvas with DrawBitmap, the first Rect represents the portion area i want to show, and the second one is the size and position of the area displayed.
In order to have the bitmap displayed on the canvas, i need to create a canvas from the Bitmap and draw it in it, in order to have it displayed. Is that correct?
So i have several questions:
I would like to understand how to vertically align the images, so they have to start from the same y (top).
I'm not sure if the vector image is the best idea, maybe is better to convert it into display dependant pngs? Or anyway the canvas size is relative to the screen? Or maybe i need to make my code screen-independent (i suppose that getIntrinsicWidth/Height are returning the real size of the image, so maybe i need to scale it?)
Why I need to explicitly setBounds of both images to have them displayed?
UPDATE #1
So i understood why probably my images are not aligned on the same line. It looks like that when creating a vector asset, for some reason the sizes are "adapted", not sure if is the android systems doing that or android studio. But anyway what i found after loading two images with the same height in pixels, is that for one i have an height, and for the other i have a different height (nearly double), and that explain why the image is shifted on the bottom.
So what i tried is to make a single image with both the notes, and the clefs, and it sort of works in the emulator. But when testing on a real device i get the error:
W/OpenGLRenderer: Bitmap too large to be uploaded into a texture (11732x1168, max=8192x8192)
Bitmap too large to be uploaded into a texture (11732x1168, max=8192x8192)
i can understand what the error means, but anyway the image size in pixel is: 2933x292 pixels. Why the getIntrinsicWidth and getIntrinsicHeight are returning that dimensions? what is their metrics?
I'm wondering that maybe the vector drawable is not the best choice? Maybe is better to cnvert it into screen-dependant pngs? and use them?

Using NinePatch (.9.png) for making scaled Bitmaps

I need to draw a Bitmap for my new Android Application. Since my content view is set to a Game-Panel class I've created. I want to display images through Bitmap. Just because its more convenient, and easier to do then anything else.
From what I've gathered from researching, Nine Patch images (.9.png) are used in android to scale more properly then regular Drawable images. It also says "This can be used for scaling properly in backgrounds. For example a regular button background...". If Nine Patch images are made to scale better. Can You use a Nine Patch for making a Bitmap in a specific width and height because it would scale more properly? For example
Bitmap b = createBitmap(BitmapFactory.decodeResource(NinePatchImage), x, y, width, height);
Is this possible? Should I do it a different way? Should I create a bitmap first and then just scale the bitmap? Is this even necessary? Please help.
Bitmap bitmap = Bitmap.createBitmap(canvasWidth, canvasHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
NinePatchDrawable drawable = (NinePatchDrawable) getResources().getDrawable(R.drawable.myDrawable);
drawable.setBounds(new Rect(x, y, width, height));
drawable.draw(canvas);

Crop Bitmap using RectF parameters on Android

Using MikeOrtiz's awesome ImageView implementation with touch and zoom events, I wanted to crop a picture taken with the camera to match the zoom. Using his method...
// Return a Rect representing the zoomed image.
RectF getZoomedRect();
...I tried cropping the resulting picture bitmap to the zoom size like so:
RectF zoomCoordinates = mTouchImageView.getZoomedRect();
Bitmap croppedBitmapToOverview = Bitmap.createBitmap(
AppResources.sCurrentImage,
((int) zoomCoordinates.left),
((int) zoomCoordinates.top),
((int) zoomCoordinates.width()),
((int) zoomCoordinates.height()));
However I get a "must be bigger than 0" error with this. While debugging I noticed ALL values were 0 due to casting to an Integer. The real values however go something like this:
//Log.d print for each of those fields without the int cast
Left 0.34047672
Top 0.20797288
Width 0.33333334
Height 0.3429547
So there's my problem, but I can't see how to fix this. I've never worked with bitmaps before or canvas, Rect, etc.
Is there some tweaking I could do to these values, or should I take a different approach altogether?
Got around the problem by simply taking a "screenshot" of the View of sorts. This got me a Bitmap with the picture as it was zoomed
mTouchImageView.setDrawingCacheEnabled(true);
AppResources.sCurrentImage = Bitmap.createBitmap(mTouchImageView.getDrawingCache());
multiply the coordinates with the size of your image like
(int)(zoomCoordinates.left * imageSize)

why getBounds( ) of Drawable remains the same even after scaling?

I used an ImageView to display a jpg file under my project 'assets' folder, the intrinsic dimensions are: 1280x854, which I confirmed by calling myDrawable.getIntrinsicWidth() and myDrawable.getIntrinsicHeight().
My image view implemented pinch zoom so the image was scaled up to 2x for example. Then the following code got executed:
Drawable drawable = getDrawable();
Rect bounds =drawable.getBounds();
Log.i("activity", String.format("drawalbe : top:%d, left: %d, (%d, %d)", bounds.top, bounds.left, bounds.width(), bounds.height()));
the drawable bounds is always 1280x854 no matter what the current scale is, and the top, left is always 0, 0.
Isn't the bounds should be scaled accordingly? Could anyone give me some hints? Thank you.
It's because of this.
Scale matrix only affects how the view is drawn, but does not change its dimensions.
Although you can create a Rect of the size of your image and apply the same scale matrix to Rect with Matrix.mapRect(Rect r) and Rect will have the scaled dimensions.

Set Picture into RemoteViews

I have some SVGs in my assets folder and I need to dynamically set them in my widget (on an ImageView).
I am using this library: http://code.google.com/p/svg-android/
This library returns a Picture or a PictureDrawable.
The only methods I can see to use on RemoteViews are setImageViewBitmap which obviously takes a bitmap.
I tried looking for code to convert a Drawable to a Bitmap like this:
PictureDrawable pictureDrawable = svg.createPictureDrawable();
Bitmap bitmap = Bitmap.createBitmap(pictureDrawable.getIntrinsicWidth(), pictureDrawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawPicture(pictureDrawable.getPicture());
currentBitmap = bitmap;
But the bitmap is too small. When I create the bitmap in Illustrator I set the artboard size to 65 which is what comes through on the intrinsic width/height.
My widgets can be resized so the ImageView sizes are variable. Even if I set the width and height statically to some large number like this...
Bitmap bitmap = Bitmap.createBitmap(300, 300, Config.ARGB_8888);
then the resulting bitmap just has a bunch of whitespace below and to the right of a tiny image.
I guess I need to somehow draw the picture at a scaled up value as well as creating the Bitmap at size 300. Ideally I could figure out the size of the ImageView at runtime and set the proper sized Bitmap if I knew that. Is this the best approach and how would I do this? Perhaps there is a better approach I don't even know about?
I've not used android-svg but if it's using vanilla PictureDrawables, then it should be just a matter of not using the intrinsic bounds.
Try the following:
PictureDrawable pictureDrawable = svg.createPictureDrawable();
Bitmap bitmap = Bitmap.createBitmap(300, 300, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
pictureDrawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
pictureDrawable.draw(canvas); // do not access the Picture directly, that defeats the purpose
currentBitmap = bitmap;
In short, use the Drawable, not its Picture and set the Drawable's bounds to be the full canvas.
Have you tried createScaledBitmap?
createScaledBitmap()
I have tried svg-android and it has not worked for me for this very reason. Not to mention its severely limited feature set.
The point of using vector graphics is that I can generate images of any appropriate size to fit the UI View size. Which means the generation method must accept the size requirements at run-time, and not always use width,height declared in <svg> tag.
Hence I used the native implementation: libsvg-android, which exactly does that.
It directly renders to a canvas with given size:
long objId = SvgRaster.svgAndroidCreate();
SvgRaster.svgAndroidParseBuffer(objId, readString(mInputStream, "UTF-8"));
SvgRaster.svgAndroidSetAntialiasing(objId, true);
SvgRaster.svgAndroidRenderToArea(objId, mCanvas, 0, 0, mWidth, mHeight);
I ended up modifying the underlying Artboard size to be 300. None of the scaling up methods worked. So the final code I used was that which I originally posted:
PictureDrawable pictureDrawable = svg.createPictureDrawable();
Bitmap bitmap = Bitmap.createBitmap(pictureDrawable.getIntrinsicWidth(), pictureDrawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawPicture(pictureDrawable.getPicture());
currentBitmap = bitmap;
Once I had a 300x300 Bitmap I was able to set it into the RemoteViews using setImageViewBitmap .
This kind of defeated the purpose of using SVGs in the first place (at least as far as using them in widgets was concerned).
My problem was not the same as User117. Perhaps he did not name the outer layer 'bounds' as was required in the library. Either way, I managed to get the library working, albeit by having to modify the SVG Artboard size.
Hopefully with the increase in screen resolution Android will introduce SVGs as part of the platform soon.

Categories

Resources