Scaling VectorDrawables on Varying Resolutions - android

Background I am currently working on creating a map with pins. For the marker and pins, we are using VectorDrawables. Our issue is with obtaining an ideal scale for the marker/pins on all devices in relation to the map. What is the best practice for handling vector scaling on multiple devices?
Note: For this question, we are only concerned with Lollipop or greater devices as we are planning on updating to the latest support library which provides support for VectorDrawables.
Images:
To demonstrate this, I have included a picture that is close to the ideal size. I do have another photo showing what it looks like on tablets, but unfortunately I am only allowed to post one link at this time.
[1] As viewed on an S6: This is an ideal size, but the difficulty is obtaining it for all devices.
Map Marker/Pins on Phone
Code
Most of what determines the overlay size is handled by the marker code and its associated vector file, but I will supply more code if needed.
Adding Overlay:
#Override
protected void onDraw(Canvas canvas)
{
// Draw backgrounds
super.onDraw(canvas);
//set up the color and the mode for the filtering function
// paint.setColorFilter(cf);
// Draw overlays
for (ImageOverlay overlay : this.overlayList)
{
Matrix overlayMatrix = overlay.getTranslationMatrix();
if (overlay.getIsScalable())
{
overlayMatrix.postConcat(getImageMatrix());
}
else
{
float[] vals = new float[]{0,0,0,0,0,0,0,0,0};
getImageMatrix().getValues(vals);
float scaleDx = overlay.getX() - (vals[Matrix.MSCALE_X]*overlay.getX());
float scaleDy = overlay.getY() - (vals[Matrix.MSCALE_Y]*overlay.getY());
overlayMatrix.postTranslate(vals[Matrix.MTRANS_X]-scaleDx, vals[Matrix.MTRANS_Y]-scaleDy);
}
canvas.drawBitmap(overlay.getImage(), overlayMatrix, paint);
}
}
Marker Code:
/**
* This method creates a bitmap for the mapMarker that we use to show where the customer is located on the map.
* #return Returns a bitmap of the marker.
*/
private Bitmap buildMapMarker(){
// Display the marker for that company
Bitmap mapMarkerBitmap;
if (Build.VERSION.SDK_INT >= 21){
VectorDrawable mapMarkerVectorDrawable = (VectorDrawable) getResources().getDrawable(R.drawable.ic_place_red_24dp);
mapMarkerBitmap = getOverlayBitmap(mapMarkerVectorDrawable, 79);
} else {
mapMarkerBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_place_red_24dp);
}
return mapMarkerBitmap;
}
/**
* This method is used to obtain the overlay bitmap for devices that are >= API 21.
* #param vectorDrawable Passed vectorDrawable to obtain info from.
* #return
*/
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private Bitmap getOverlayBitmap(VectorDrawable vectorDrawable, int width) {
Bitmap bitmap = Bitmap.createBitmap(width,
width, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
vectorDrawable.draw(canvas);
return bitmap;
}
Marker Vector XML:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zm0,9.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"
android:fillColor="#F44336"/>
Attempts I have made:
Using the vectors own dpi width/height ratio, this resulted in the markers being too large on higher dpi devices such as the s6 and small on devices such as the Nexus 7
Setting a fixed width and height for the canvas, this resulted in a much larger marker on low dpi devices vs high dpi devices.
Initially, we made individual pngs for each device and this was the most ideal solution thus far, but we were hoping to implement vectors in some way if possible.
Note:
Please let me know if more information or code is needed to provide a sufficient response.

Related

Xamarin.Andoid: Merge ImageView with CanvasView

I am using the FingerPaintCanvasView from this xamarin sample.
I am working with 2 layers. The first layer is an ImageView i want to draw on. The second layer is the PaintCanvasView to draw.
<RelativeLayout
android:layout_height="match_parent"
android:layout_width="match_parent">
<ImageView
android:id="#+id/markImageImageView"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
<fingerpaint.FingerPaintCanvasView
android:id="#+id/canvasMarkMeta"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
The canvas has a transparent background and the layout params of both views are set programmatically. This way the marking works fine.
Now the question is, how can I merge this imageview with the marking canvas with as little as possible quality loss to an singel file (bitmap or imageFile in FileSystem).
Let me explain why I mentiond the quality loss:
For example the image in the background has a size of 1920x1080 from the devices camera. The display only has 1280x800 pixel. Since I can't fit the image in the display, I need to display a scaled down version AND the marking happens on this scaled down version.
EDIT:
#Joe LV:
This is your demo without any changes deployed on my devices:
Lenovo Yoga 3, Android 6.0.1
Huawei Honor 8, Android 7.0
I will try an Android 8 Emulator soon.
Pixel 2XL, Android 8.1
So this method does not Work for API <= 24 :-(
(API 25 and 26 not tested)
markImageImageView just holds an image that I load from the device storage (can be any image)
canvasMarkMeta is the FingerPaintCanvas from the linked template which the holds the drawn lines.
Below is the code, I have add comments in it:
public class MainActivity : Activity
{
private Rect mSrcRect, mDestRect;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
//your background picture ---markImageImageView
Bitmap background = BitmapFactory.DecodeResource(Resources, Resource.Drawable.pause);
//your foreground picture ---FingerPaintCanvasView
Bitmap foreground = BitmapFactory.DecodeResource(Resources, Resource.Drawable.play);
Paint p = new Paint();
p.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.SrcOver));
//use background to create a canvas
Bitmap workingBitmap = Bitmap.CreateBitmap(background);
Bitmap mutableBitmap = workingBitmap.Copy(Bitmap.Config.Argb8888, true);
Canvas c = new Canvas(mutableBitmap);
int mBWith = background.Width;
int mBHeight = background.Height;
int mFWith = foreground.Width;
int mFHeight = foreground.Height;
mSrcRect = new Rect(0, 0, mBWith, mBHeight);
mDestRect = new Rect(0, 0, mFWith, mFHeight);
//draw foreground on the backaground, then they will be single bitmap
c.DrawBitmap(foreground, mSrcRect, mDestRect, p);
ImageView imageView = FindViewById<ImageView>(Resource.Id.iv);
imageView.SetImageBitmap(mutableBitmap);
}
}
And I have provide the demo on github.
Update:
Change Bitmap.Config.Argb4444 to Bitmap.Config.Argb8888.

AndroidSVG fuzzy edges on image

I am want to display Barcode on android. As input I get SVG string. As a SVG library I use AndroidSVG. I used sample code from library website and everything seem to be fine. But when I zoom on image, I get distorted edges (Anti-alias?). I tried to disable all the flags. But the image still has fuzzy edges. What can be wrong with my code?
Picture:
Try to zoom to max, you will see the fuzzy edges.
Code:
private void loadQRCode(String svgString) {
SVG svg = null;
try {
svg = SVG.getFromString(svgString);
} catch (SVGParseException e) {
e.printStackTrace();
}
if (svg.getDocumentWidth() != -1) {
int widthPx = Utils.pxFromDp(400);
int heightDp = Utils.pxFromDp(300);
svg.setDocumentWidth(widthPx);
svg.setDocumentHeight(heightDp);
int width = (int) Math.ceil(svg.getDocumentWidth());
int height = (int) Math.ceil(svg.getDocumentHeight());
Bitmap newBM = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas bmcanvas = new Canvas(newBM);
final DrawFilter filter = new PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG| Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG, 0);
bmcanvas.setDrawFilter(filter);
barcode.setLayerType(View.LAYER_TYPE_SOFTWARE,null);
bmcanvas.drawRGB(255, 255, 255);
svg.renderToCanvas(bmcanvas);
barcode.setImageBitmap(newBM);
}
}
If the edges of the bars do not lie exactly on pixel boundaries, you will get anti-aliasing. On a high resolution screen, this should not normally be visible.
However, in your code, you are rendering the SVG to a bitmap and setting the bitmap to an ImageView. If that ImageView has a size larger than the bitmap - ie. greater than 400 x 300, then the anti-aliased pixels in that bitmap will likely be rendered larger and thus more visible.
One solution is to avoid using a bitmap. Use a Picture/PictureDrawable instead. That way the barcode will be rendered at highest quality no matter what size it is. As vector graphics are supposed to be.
Follow the example on this page:
http://bigbadaboom.github.io/androidsvg/use_with_ImageView.html
So your code should probably look something like the following:
private void loadQRCode(String svgString) {
try {
SVG svg = SVG.getFromString(svgString);
barcode.setLayerType(View.LAYER_TYPE_SOFTWARE,null);
Drawable drawable = new PictureDrawable(svg.renderToPicture());
barcode.setImageDrawable(drawable);
} catch (SVGParseException e) {
e.printStackTrace();
}
}
If for some reason you need to use bitmaps - maybe you are caching them or something - then you should watch for changes in the size of the ImageView and then recreate the bitmap at the new size. So the bitmap is always the same size as the ImageView to which it is assigned.

How to show both MarkerIcon and Title in google map as like Google Apps?

When I find nearest restaurants in Google's Maps Android Application the restaurant name is showing near to marker icon as default. (Refer Google Image).
But in my application I need to do same when I search for nearest restaurants, I able to display only marker icons. (Refer My Application Image).
Google Image:
My Application Image:
Partial Solution :
Here I found partial solution for this we get this by drawing a text using canvas. I used below code refer by these links here and here But canvas drawing cutting of my text. Refer attached image TextDrawn Image
Marker myLocMarker = map.addMarker(new MarkerOptions()
.position(myLocation)
.icon(BitmapDescriptorFactory.fromBitmap(writeTextOnDrawable(R.drawable.bluebox, "your text goes here"))));
private Bitmap writeTextOnDrawable(int drawableId, String text) {
Bitmap bm = BitmapFactory.decodeResource(context.getResources(), drawableId)
.copy(Bitmap.Config.ARGB_8888, true);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
paint.setTextAlign(Paint.Align.CENTER);
paint.setLinearText(true);
paint.setAntiAlias(true);
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
paint.setTextSize(35);
Rect textRect = new Rect();
paint.getTextBounds(text, 0, text.length(), textRect);
Canvas canvas = new Canvas(bm);
//Calculate the positions
// int xPos = (canvas.getWidth() / 2) - 2; //-2 is for regulating the x position offset
//"- ((paint.descent() + paint.ascent()) / 2)" is the distance from the baseline to the center.
// int yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2)) ;
canvas.drawText(text, canvas.getHeight() + 2, canvas.getHeight() + 2, paint);
return bm;
}
There are several sources for icons, and Places API provides enough details so that you can pick the right icon for each place. I'm not familiar with Android though, so I'll only touch lightly on that.
The way things are today, I'd sayd the trick is to find a dependable set of icons you can work with.
Safest option may be Place API's own icons. At least in the web service, each place as an icon field that points to a URL. Being part of a supported API, I'd feel most comfortable depending on these icons ─ save for having my own icons. Here's the one for bars:
If you like them, and don't mind that they may go away some day without notice, you could use the deprecated Chart API's Freestanding Icons. At least this ones will only change once: from being available to no longer being available. They don't look particularly good though:
If you really really want the same icons used in Google Maps search results, and enjoy living on edge, you could hack their URLs out of Google Maps. Beware: these may change at any time, so you might one day wake up to no icons! ─ and it may change often, not just once.
However, if you're using Places API for Android, I can imagine you're looking for a solution based off of the Places API web service.
It looks like the Place object has no getIcon() or anything similar, so you'd need to come up with your own mapping from the output of getPlaceTypes() to icons. I think it'd be reasonable to ask Google to add a getIcon() method that would do this for you, so I recommend filing a feature request for it.
Try with the custom map marker. You can create your own view and set that view to the marker.
Step 1 :
Create a custom layout file based on your needs and inflate it.
View customMarker = LayoutInflater.from(getApplicationContext()).inflate(R.layout.custom_marker, null);
Step 2 :
Convert custom view to Bitmap.
public static Bitmap createDrawableFromView(Context context, View view) {
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
view.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
view.measure(displayMetrics.widthPixels, displayMetrics.heightPixels);
view.layout(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
view.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
Bitmap customeViewBitmap = createDrawableFromView(context, customMarker);
Step 3 : Create the marker with the resulted bitmap and add it to google map object.
GoogleMap googleMap = googleMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(
R.id.map)).getMap();
googleMap.addMarker(new MarkerOptions()
.position(latLng) .icon(BitmapDescriptorFactory.fromBitmap(customeViewBitmap)));
Step 4 :
Add MarkerClick Listener
googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
#Override
public boolean onMarkerClick(Marker marker) {
//Your logic here
return false;
}
});
Those aren't Markers. I believe they're added to Google Places database, using the Places Add API.
What I can suggest, is for you to retrieve the result from places, and use those results as title when you're adding Markers.
My approach to your situation would be to use Dynamic Icons from Google Maps.
It may be deprecated, but I am unaware of a better alternative at the moment. With this library you would be able to create pins with icons and letters inside, which I believe is exactly what you want.
For example, the following link
https://chart.googleapis.com/chart?chst=d_map_spin&chld=2.1|35|FFFF88|11|_|A|Balloon!
will create the following image
The library is extensive and has alot of options, this is just the tip of the iceberg.
Hope it helps!
After few days research I finishing with this solution.
We can set your own layout to IconGenerator
View marker = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.custom_marker_layout, null);
TextView numTxt = (TextView) marker.findViewById(R.id.num_txt);
numTxt.setText("Naveen");
final IconGenerator mIconGenerator = new IconGenerator(getApplicationContext());
mIconGenerator.setContentView(marker);
Bitmap icon = mIconGenerator.makeIcon();
customMarker = mMap.addMarker(new MarkerOptions()
.position(markerLatLng)
.title("Title")
.snippet("Description")
.icon(BitmapDescriptorFactory.fromBitmap(icon))
.anchor(0.5f, 1));

Draw bitmap over bitmap and set some pixels transparent

I have a custom View and two Bitmaps. I draw the one above the other like this
canvas.drawBitmap(backImage,0,0,null);
canvas.drawBitmap(frontImage,0,0,null);
Before painting I set some pixels in the frontImage transparent using the setPixel(...) function of Bitmap
frontImage.setPixel(x,y, Color.TRANSPARENT);
Instead of viewing backImage's pixels at x,y I see black color...
There may be a really simple solution to this. What is your source material for the images? If you are loading them from files all you may need to do is convert the source files to PNG's. PNG's maintain transparency information and most rendering engines will take this in to account when layering them together on the screen.
Another Possibility. I use this technique quite a bit in Java games on the PC. It uses a little transparency class I stumbled across a number of years ago at this location:
/*************************************************************************
* The Transparency class was also developed by a thrid party. Info
* on its use can be found at:
*
* http://www.rgagnon.com/javadetails/java-0265.html
*
*************************************************************************/
//Transparency is a "Static", "Inner" class that will set a given color
//as transparent in a given image.
class Transparency {
public static Image set(Image im, final Color color) {
ImageFilter filter = new RGBImageFilter() { //Inner - Inner class -- very bad
// the color we are looking for... Alpha bits are set to opaque
public int markerRGB = color.getRGB() | 0xFF000000;
public final int filterRGB(int x, int y, int rgb) {
if ( ( rgb | 0xFF000000 ) == markerRGB ) {
// Mark the alpha bits as zero - transparent
return 0x00FFFFFF & rgb;
}
else {
// nothing to do
return rgb;
}
}
};
//apply the filter created above to the image
ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(ip);
}
}
Taking a Java Image object as input it will take what ever color you give it and perform the math on the image turning the color transparent.
Good luck.

Android MapView custom markers are all the same size

I am writing an app that will show a map with a bunch of markers on it. I want the markers to be dynamic (showing dynamic content on them). Because the markers content is dynamic the size of each marker is different.
I found this in the documentation saying that "Different markers can be returned for different states. The different markers can have different bounds." here: https://developers.google.com/maps/documentation/android/reference/com/google/android/maps/OverlayItem#getMarker(int)
I assume that means that multiple markers on the same overlay can be different sizes. Is that correct?
I have an ArrayList on my overlay (extends BalloonItemizedOverlay). The overlay's createItem() method looks like this:
#Override
protected OverlayItem createItem(final int index)
{
final Person person = people.get(index);
final GeoPoint gp = new GeoPoint(person.getLat(), person.getLng());
final OverlayItem oi = new OverlayItem(gp, person.getName(), person.getName());
oi.setMarker(new PersonDrawable(defaultMarker, person));
return oi;
}
Then in my PersonDrawable there is a drawable that has a 9 patch as a background image, and then drawing text on top of it:
public PersonDrawable(final Drawable iBackground, final Person person)
{
background = iBackground;
text = person.getName();
padding = new Rect();
if (background instanceof NinePatchDrawable)
{
// This is getting hit, and just sets the value of the padding
final NinePatchDrawable ninePatch = (NinePatchDrawable) background;
ninePatch.getPadding(padding);
}
// set the bounds of the marker
bounds = background.getBounds();
paint = new Paint();
paint.setColor(Color.rgb(255, 255, 255));
paint.setFakeBoldText(true);
paint.setTextSize(30);
// find the bounds of the text
final Rect textBounds = new Rect();
paint.getTextBounds(text, 0, text.length(), textBounds);
// set the left and right bounds to that of the text plus the padding (then center it)
bounds.left = 0 - (textBounds.width() / 2) - padding.left;
bounds.right = 0 + (textBounds.width() / 2) + padding.right;
// set the bounds of the drawable
setBounds(bounds);
}
#Override
public void draw(final Canvas canvas)
{
// when it's time to draw, draw the background
background.draw(canvas);
// then draw the text within the padding
canvas.drawText(text, bounds.left + padding.left, bounds.bottom - padding.bottom, paint);
}
Each marker has a distinct bounds set, even after it's drawn on the overlay its bounds are distinct. The MapView (or Overlay) is not reusing the objects from createItem(). However the Overlay seems to always pick one marker and assume that is the size for all the markers. Is that just the behavior? Is there something I can do to make it respect the bounds differently for each marker?
Here are some pictures, when it decides to pick a larger marker things turn out ok:
However when it chooses to use the smaller marker as the size:
The problem is that when a Drawable is loaded from resources, it is treated as a shareable resources. From the Drawable documentation:
By default, all drawables instances loaded from the same resource share a common state; if you modify the state of one instance, all the other instances will receive the same modification.
In the OverlayItem, it is assuming that the Drawable is going to be the same size throughout. The effect you are seeing is that the last modification to the Drawable is overriding all previous modifications, so all displays of the Drawable are showing up exactly the same.
The solution is to prevent sharing of the Drawable's modifications. You can do this using the mutate() method in conjunction with multiple loads of the Drawable from a resource. You should do this in conjunction with the PersonDrawable constructor you have outlined above.

Categories

Resources