Android Drawable looks ugly due to scaling - android

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.

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?

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)

Using a rounded corners drawable

There is a nice post made by the popular Google developer Romain Guy that shows how to use a rounded corners drawable (called "StreamDrawable" in his code ) on a view efficiently.
The sample itself works very well on my Galaxy S3 when in portrait mode, but I have a few issues with it:
if the screen is small (for example on qvga screens), the shown images get cropped.
if I have an input bitmap that is too small than how I wish to show it, the output image has its edges smeared. Even on the Galaxy S3, when you run the sample code and it's on landscape, it looks awful:
I'm still not sure about it (since I use a workaround of scaling the image for using the sample code), but it think that even this solution is a bit slow when being used in a listView. Maybe there is a renderscript solution for this?
It doesn't matter if I use setImageDrawable or setBackgroundDrawable. It must be something in the drawable itself.
I've tried to play with the variables and the bitmapShader, but nothing worked. Sadly TileMode doesn't have a value for just stretching the image, only tiling it in some way.
As a workaround I can create a new scaled bitmap, but it's just a workaround. Surely there is a better way which will also not use more memory than it should.
How do I fix those issues and use this great code?
I think that the solution that is presented on this website works well.
unlike other solutions, it doesn't cause memory leaks, even though it is based on Romain Guy's solution.
EDIT: now on the support library, you can also use RoundedBitmapDrawable (using RoundedBitmapDrawableFactory ) .
I had some size issues with this code, and I solved it.
Maybe this will help you, too:
1) in the constructor store the bitmap in a local variable (e.g. private Bitmap bmp;)
2) override two more methods:
#Override
public int getIntrinsicWidth() {
return bmp.getWidth();
}
#Override
public int getIntrinsicHeight() {
return bmp.getHeight();
}
Best regards,
DaRolla
There underlying problem is that the BitmapShader's TileMode doesn't have a scaling option. You'll note in the source that it's been set to Shader.TileMode.CLAMP, and the docs describe that as:
replicate the edge color if the shader draws outside of its original bounds
To work around this, there are three solutions:
Constrain the size of the view in which the drawable is used to the size of the bitmap.
Constrain the drawing region; for instance, change:
int width = bounds.width() - mMargin;
int height = bounds.height() - mMargin;
mRect.set(mMargin, mMargin, width, height);
To:
int width = Math.min(mBitmap.getWidth(), bounds.width()) - mMargin;
int height = Math.min(mBitmap.getHeight(), bounds.height()) - mMargin;
mRect.set(mMargin, mMargin, width, height);
Scale the bitmap to the size of the drawable. I've moved creating the shader into onBoundsChange() and have opted to create a new bitmap from here:
bitmap = Bitmap.createScaledBitmap(mBitmap, width, height, true);
mBitmapShader = new BitmapShader(bitmap,
Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Note that this a potentially slow operation and will be running on the main thread. You might want to carefully consider how you want to implement it before you go for this last solution.

Android Bitmap Color Modify in Real Time

I have a imageview that I want to change the color based on a user choice
From examples on the internet I see the only way to really do this is by going through and modifying each pixel... however it seems to be EXTREMELY slow
if I add this into my code, it takes long enough that it prompts the user to force close or wait
for(int i =0 ; i < mBitmap.getHeight(); ++i)
{
for(int g = 0; g < mBitmap.getWidth(); ++g)
{
}
}
What is the best way to change the color of the image?
The Image is a small image 320x100 and is mostly transparent with a small image in the inside, the small image I want to change the color of
The Problem lies in using getPixel(x,y). Grabbing each pixel one by one is a very slow process. Instead use getPixels
void getPixels(int[] pixels, int offset, int stride, int x, int y, int width, int height)
Returns in pixels[] a copy of the data in the bitmap.
It'll return you an array of integers with the pixel values (and operate on that array and then use setPixels) and it will be faster (although requires more memory)
For a small image this method will do. Stride is equal to the image width.
mBitmap.getPixels( pixels, 0 , mBitmap.getWidth(), 0, 0 , mBitmap.getWidth(), mBitmap.getHeight());
In order of complexity:
Check the samples in API demos for usage of ColorFilter and ColorMatrix. But since you described it as a image within an image that you are trying to modify, this may not apply.
Put your processing code on its own thread to avoid the Application Not Responding issue. Look into AsyncTask. You may need to have a wait animation running while its processing.
Consider OpenGL ES 1.x. Use the image as a texture and overlay a color with alpha to get the effect. Although this would have better performance, the complexity of adding UI elements would need to be taken into account (i.e. build your own).

Animating and rotating an image in a Surface View

I would like to animate movement on a SurfaceView . Ideally I would like to also be notified once the animation had finished.
For example:
I might have a car facing North. If I wanted to animate it so that it faces South for a duration of 500ms, how could I do that?
I am using a SurfaceView so all animation must be handled manually, I don't think I can use XML or the android Animator classes.
Also, I would like to know the best way to animate something continuously inside a SurfaceView (ie. a walk cycle)
Rotating images manually can be a bit of a pain, but here's how I've done it.
private void animateRotation(int degrees, float durationOfAnimation){
long startTime = SystemClock.elapsedRealtime();
long currentTime;
float elapsedRatio = 0;
Bitmap bufferBitmap = carBitmap;
Matrix matrix = new Matrix();
while (elapsedRatio < 1){
matrix.setRotate(elapsedRatio * degrees);
carBitmap = Bitmap.createBitmap(bufferBitmap, 0, 0, width, height, matrix, true);
//draw your canvas here using whatever method you've defined
currentTime = SystemClock.elapsedRealtime();
elapsedRatio = (currentTime - startTime) / durationOfAnimation;
}
// As elapsed ratio will never exactly equal 1, you have to manually draw the last frame
matrix = new Matrix();
matrix.setRotate(degrees);
carBitmap = Bitmap.createBitmap(bufferBitmap, 0, 0, width, height, matrix, true);
// draw the canvas again here as before
// And you can now set whatever other notification or action you wanted to do at the end of your animation
}
This will rotate your carBitmap to whatever angle you specify in the time specified + the time to draw the last frame. However, there is a catch. This rotates your carBitmap without adjusting its position on screen properly. Depending on how you're drawing your bitmaps, you could end up with your carBitmap rotating while the top-left corner of the bitmap stays in place. As the car rotates, the bitmap will stretch and adjust to fit the new car size, filling the gaps around it with transparent pixels. It's hard to describe how this would look, so here's an example rotating a square:
The grey area represents the full size of the bitmap, and is filled with transparent pixels. To solve this problem, you need to use trigonometry. It's a bit complicated... if this ends up being a problem for you (I don't know how you're drawing your bitmaps to the canvas so it might not be), and you can't work out the solution, let me know and I'll post up how I did it.
(I don't know if this is the most efficient way of doing it, but it works smoothly for me as long as the bitmap is less than 300x300 or so. Perhaps if someone knows of a better way, they could tell us!)
Do you want multiple independent animated object? If so, then you should use a game loop. (One master while loop that incrementally updates all game objects.) Here's a good discussion on various loop implementations. (I'm currently using "FPS dependent on Constant Game Speed" for my Android game project.)
So then your Car will look something like this (lots of code missing):
class Car {
final Matrix transform = new Matrix();
final Bitmap image;
Car(Bitmap sprite) {
image = sprite; // Created by BitmapFactory.decodeResource in SurfaceView
}
void update() {
this.transform.preRotate(turnDegrees, width, height);
}
void display(Canvas canvas) {
canvas.drawBitmap(this.image, this.transform, null);
}
}
You only need to load your bitmap once. So if you have multiple Cars, you may want to give them each the same Bitmap object (cache the Bitmap in your SurfaceView).
I haven't got into walk animations yet, but the simplest solution is to have multiple Bitmaps and just draw a different bitmap each time display is called.
Have a look at lunarlander.LunarView in the Android docs if you haven't already.
If you want to be notified when the animation is complete, you should make a callback.
interface CompletedTurnCallback {
void turnCompleted(Car turningCar);
}
Have your logic class implement the callback and have your Car call it when the turn's complete (in update()). Note that you'll get a ConcurrentModificationException if you are iterating over a list of Cars in update_game() and you try to remove a Car from that list in your callback. (You can solve this with a command queue.)

Categories

Resources