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?
Related
I am developing a photography app in which I want to select a particular section of a photo and stretch that portion only. How to do that?
I have tried to stretch a photo using Canvas but failed to do so. Is it possible from android.graphics.NinePatch class?
Any suggestions?
You can use matrix to apply new dimension to your bitmap.
you could use setscale/postScale methods of a matrix object.
A rather ugly solution would be using a image cropping library. You can use it to temporarily crop a portion of the image and load it into another ImageView and then scaling it up.
Also, you can get things done without using that CropImageView. The idea is whenever user touches the original image view, you will be given a (x, y) at which user's finger resides. So, you can extract a bitmap image centered at (x, y) and with a given radius.
For applying a magnifier effect you could use a round/circular ImageView for showing magnified portion of the image.
Something like this:
Finally i found a solution of my problem below :
Bitmap stretchImage = Bitmap.createBitmap(w, h+progress, Bitmap.Config.ARGB_8888 );
c = new Canvas(stretchImage);
//draw top bit
c.drawBitmap(normalImage, new Rect(0,0,w,75), new Rect(0,0,w,75), null);
//draw middle bit
c.drawBitmap(normalImage, new Rect(0,75,w, 150), new Rect(0,75,w,150+progress), null);
//draw right bit
c.drawBitmap(normalImage, new Rect(0 ,150,w,225), new Rect(0 ,150+progress,w,225+progress), null);
myImage.setImageBitmap(stretchImage);
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)
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.
I'm trying to create a 'glow' effect using the Android Path class. However, the gradient is not being warped to fit around the path. Instead, it is simply being display 'above' it and clipped to the path's stroke. Using a square path, the image below shows what I mean:
Instead, that should look more like this:
In other words, the gradient follows the path, and in particular wraps around the corners according to the radius set in the CornerPathEffect.
Here is the relevant part of the code:
paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(20);
paint.setAntiAlias(true);
LinearGradient gradient = new LinearGradient(30, 0, 50, 0,
new int[] {0x00000000, 0xFF0000FF, 0x00000000}, null, Shader.TileMode.MIRROR);
paint.setShader(gradient);
PathEffect cornerEffect = new CornerPathEffect(10);
paint.setPathEffect(cornerEffect);
canvas.drawPath(boxPath, paint);
Any ideas?
Another alternative is to get a 'soft-edged brush' effect when defining the stroke width. I've experimented with BlurMaskFilters, but those give a uniform blur rather than a transition from opaque to transparent. Does anyone know if that's possible?
How about drawing with a soft brush bitmap? Make a soft circular brush with opacity decreasing radially outward using image editing software like Photoshop. Save as drawable, load it in a bitmap and draw it evenly spaced along your path. Make the bitmap with white coloured brush. This way you can simply multiply the given colour(Here blue) to your bitmap using PorterDuffColorFilter.
brush1=BitmapFactory.decodeResource(getResources(), R.drawable.brush_custom_one);
//This contains radially decreasing opacity brush
porter_paint.setColorFilter(new PorterDuffColorFilter(paint.getColor(), Mode.MULTIPLY));
for (int i=1;i<matrix.size();i++) {
//matrix contains evenly spaced points along path
Point point = matrix.get(matrix.get(i));
canvas.drawBitmap(brush1, point.x,point.y, porter_paint);}
The brush used is (It's there):
The final result is:
Turns out there was a stupidly obvious way of doing this. Simply re-use the same path, and adjust the stroke width and alpha on each drawing pass. Example code:
float numberOfPasses = 20;
float maxWidth = 15;
for (float i = 0; i <= numberOfPasses; i++){
int alpha = (int) (i / numberOfPasses * 255f);
float width = maxWidth * (1 - i / numberOfPasses);
paint.setARGB(alpha, 0, 0, 255);
paint.setStrokeWidth(width);
canvas.drawPath(path, paint);
}
See below for an example of the result. The left path was drawn using this method, the right path, for comparison, is drawn in a single stroke with maxWidth and 255 alpha.
This mainly works. There are two problems:
The gradient isn't as smooth as it could be. This is because each pass being drawn over the previous one results in the alpha building up too quickly, reaching 255 before the final strokes. Experimenting a bit with the line int alpha = (int) (i / numberOfPasses * 125f); (note the change to 125f rather than 255f) helps.
The path looks like it has been 'cut' on the insides of the corners. Probably some result of the CornerPathEffect applied.
What you're wanting to do, if I understand it right, is to have the gradient effectively form a "brush" for the stroke.
This is exactly what I also was trying to achieve recently, but as far as I can tell the API doesn't provide any straightforward means to do it. I have recently created an SVG to Android Canvas converter class and so I am working a lot in Inkscape lately, too. So, when I was looking into it, I wondered if it's even possible to do it in Inkscape. However, even in Inkscape it's a very non-trivial thing to do. After some searching I eventually came across this image of a gradient being applied along the course of a path, together with a download link for a tutorial beneath:
http://www.flickr.com/photos/35772571#N03/3312087295/
What I was personally trying to do at the time was to create some semi-circles where the path is a kind of neon glow as opposed to a flat colour. Talking in terms of both the Android API and the SVG standard, it seems that the only way to to do this is to create a radial gradient that's centred perfectly on the circle, and position a series of color stops in exactly the right places. Pretty tricky to do, and I certainly don't know how you'd do it to a shape like a square.
Sorry that this is a bit of a 'I couldn't do it either' rather than a useful answer! I'll follow this with interest as I'm eager to know a solution for a kind of 'soft brush' effect too.
Can be very complicated to draw a gradient than follow a path.
So I suggest you to use some library already done than make it for you.
One can be Sc-Gauges.
Have some usefully classe than you can use for your goal.
For first include the library:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
After create an image or what you want with a canvas where draw:
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Now the code:
// Dimensions
int padding = 24;
Rect drawArea = new Rect(padding, padding, 700 - padding, 500 - padding);
// Get the main layout
ImageView imageContainer = (ImageView) this.findViewById(R.id.image);
assert imageContainer != null;
// Create a bitmap and link a canvas
Bitmap bitmap = Bitmap.createBitmap(
drawArea.width() + padding * 2, drawArea.height() + padding * 2,
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.parseColor("#f5f5f5"));
// Create the path building a bezier curve from the left-top to the right-bottom angles of
// the drawing area.
Path path = new Path();
path.moveTo(drawArea.left, drawArea.top);
path.quadTo(drawArea.centerX(), drawArea.top, drawArea.centerX(), drawArea.centerY());
path.quadTo(drawArea.centerX(), drawArea.bottom, drawArea.right, drawArea.bottom);
// Feature
ScCopier copier = new ScCopier();
copier.setPath(path);
copier.setColors(Color.RED, Color.GREEN, Color.BLUE);
copier.setWidths(20);
copier.draw(canvas);
// Add the bitmap to the container
imageContainer.setImageBitmap(bitmap);
And this the result:
The first part of the code is just for create a bitmap where draw.
What you interest is the second part where use ScCopier.
Just give the path, the color and the with.
Note than is you are inside a view you can use onDraw for draw directly on the view canvas.
This library can used to create gauge of every kind.
If you want take a look to this site ScComponents have some free and not gauges components.
I am writing a View that should show a drawable that seems to "never end".
It should be twice or third the displaysize and move slow through the display.
Therefore I studied some samplecode by Google and found the important Lines
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
canvasWidth = width;
canvasHeight = height;
float sf = backgroundImage.getWidth() / canvasWidth;
backgroundImage = Bitmap.createScaledBitmap(backgroundImage,
(int) (canvasWidth * sf), canvasHeight, true);
}
To rescale the image and than
// decrement the far background
backgroundXPos = backgroundXPos - DELTAMOVE;
// calculate the wrap factor for matching image draw
int newFarX = backgroundImage.getWidth() - (-backgroundXPos);
// if we have scrolled all the way, reset to start
if (newFarX <= 0) {
backgroundXPos = 0;
// only need one draw
canvas.drawBitmap(backgroundImage, backgroundXPos, 0, null);
} else {
// need to draw original and wrap
canvas.drawBitmap(backgroundImage, backgroundXPos, 0, null);
canvas.drawBitmap(backgroundImage, newFarX, 0, null);
}
To draw the moving image. The images is already moving, it's fine.
But, and this is the point of my question, the image looks very ugly. Its original is 960*190 pixels by 240ppi. It should be drawn inside a view with 80dip of height and "fill_parent" width.
It should look same (and good) on all devices. I have tried a lot but I don't know how to make the picture look nice.
Thanks for your help.
Best regards,
Till
Since you're saying that it's a never ending drawable, probably you're writing a game of some sort. If your image is a pixel-art type, then you don't want any scaling; pixel-art-type images cannot be scaled and keep its crisp look (you can try using nearest neighbor interpolation and scaling to an integer multiple of the original, which sometimes might work, but sometimes you will still need manual tweaks). This is the rare case where you actually would need to have different image resource for different screen resolutions.
Otherwise you might want to use a vector image, but if -- as you said -- your original is a high resolution image, then vector image probably won't help much here.
btw, you probably want to show some screenshot. "Looks ugly" is just as helpful as saying my code does not work.
Just a guess, but instead of passing a null paint to your drawBitmap() calls, try making a paint with bitmap filtering disabled:
Paint p = new Paint();
p.setFilterBitmap(false);
canvas.drawBitmap(backgroundImage, backgroundXPos, 0, p);
Hope that helps.