I'm trying to visualize a rotating object on an Android 9 device by programmatically rotating a vector drawable, created from a .svg file using the New->Vector Asset method. The rotation of the object should follow the changes of some external property, instead of a predefined animation.
When a vector image is drawn on an ImageView as a VectorDrawable, the image produced has smooth edges as it should, but when the image is rotated programmatically using a RotateDrawable, the edges become jagged, as if the image was treated as a bitmap, and not redrawn as vector graphics.
The image below illustrates this problem:
According to the VectorDrawable documentation, a bitmap cache is created for each vector asset when it is first loaded, but is there a possibility to force the re-rendering when the image is rotated?
Below is some sample code used to create the effect.
Drawable class:
public class MyDrawable extends Drawable implements Drawable.Callback, Runnable {
#Override
public void draw(Canvas canvas)
{
VectorDrawable vectorDrawable = (VectorDrawable)mainActivity.getResources().getDrawable(R.drawable.test_drawable, mainActivity.getTheme());
vectorDrawable.setBounds(canvas.getClipBounds());
vectorDrawable.draw(canvas); // This looks OK
RotateDrawable rotator = new RotateDrawable();
rotator.setBounds(canvas.getClipBounds());
rotator.setLevel(1000);
rotator.setDrawable(vectorDrawable.mutate());
rotator.draw(canvas); // Jagged edges
}
}
Used in an ImageView in a Fragment:
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
final ImageView testView = (ImageView)getView().findViewById(R.id.test_view);
testView.setImageDrawable(new MyDrawable());
}
There seems to be no built-in support currently in Android for what I'm trying to achieve, but I found at least one working solution, which is to use the third-party AndroidSVG library and modify the .svg upper-most group <g> element directly to include a transformation:
#Override
public void draw(Canvas canvas)
{
try {
String svgString = // Your .svg file as plaintext
svgString = svgString.replaceFirst("<g>", "<g transform=\"rotate(" + angle + ", " + pivotX + " , " + pivotY + ")\">");
SVG svg = SVG.getFromString(svgString);
svg.renderToCanvas(canvas);
// ...
}
catch (SVGParseException svgpe){}
}
Related
I am creating an app that pulls images from urls and puts them into a recyclerview. The user can then access those images and view it fullscreen. This is achieved with Picasso. I would now like the ability to fingerpaint over the image loaded with Picasso with an onTouchEvent or something but not sure how to do it.
This class sets the image to a map_edit_gallery.xml loaded with Picasso:
public class EditMapImage extends AppCompatActivity {
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map_edit_gallery);
checkIntent();
//Find savebutton
ImageButton saveMapButton = findViewById(R.id.saveEditImagebutton);
saveMapButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(),"Saved",Toast.LENGTH_SHORT).show();
}
});
}
//This will check to see if the intent extras exist and if they do get the extra
private void checkIntent(){
if(getIntent().hasExtra("image_url") && getIntent().hasExtra("name_url")){
String imageUrl = getIntent().getStringExtra("image_url");
String nameUrl = getIntent().getStringExtra("name_url");
setMapImage(imageUrl, nameUrl);
}
}
private void setMapImage(String imageUrl, String nameUrl){
//Set the Text view
TextView name = findViewById(R.id.mapNameEditor);
name.setText(nameUrl);
//Set the Image
ImageView imageView = findViewById(R.id.mapEditScreen);
Picasso.get().load(imageUrl).into(imageView);
Picasso picasso = Picasso.get();
DrawToImage myTransformation = new DrawToImage();
picasso.load(imageUrl).transform(myTransformation).into(imageView);
}
}
EDIT:
This class has allowed me to draw over the loaded image using canvas but cannot figure out how to use touch to draw:
public class DrawToImage implements Transformation {
#Override
public String key() {
// TODO Auto-generated method stub
return "drawline";
}
public Bitmap transform(Bitmap bitmap) {
// TODO Auto-generated method stub
synchronized (DrawToImage.class) {
if(bitmap == null) {
return null;
}
Bitmap resultBitmap = bitmap.copy(bitmap.getConfig(), true);
Canvas canvas = new Canvas(resultBitmap);
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10);
canvas.drawLine(0, resultBitmap.getHeight()/2, resultBitmap.getWidth(), resultBitmap.getHeight()/2, paint);
bitmap.recycle();
return resultBitmap;
}
}
}
Try using the image selected by the user to set it in a canvas object and draw on the canvas object itself, as opposed to the image. There are plenty of tutorials out there to help you with how to draw on a canvas.
This process isn't connected with the Picasso Image Library in any way so I would recommend first getting the image through Picasso, then sending the image into your custom canvas implementation, then returning a bitmap/drawable which you could set into Picasso after editing.
There's also plenty of tutorials on how to export an image from the canvas to get your edited image when you need it.
I hope this helped, Panos.
When adding markers in mapbox, there is a provision to add custom icon to the marker. I wonder whether we can inflate a view(R.layout file) instead of assigning a drawable icon.
Here is the code:-
public void onMapReady(MapboxMap mapboxMap) {
IconFactory iconFactory=IconFactory.getInstance(context);
for(int i=0;i<coordinates.size();i++){
mapboxMap.addMarker(new MarkerOptions()
.position(new LatLng(lat,longt))
.icon(iconFactory.fromResource(R.drawable.ic_location_green))
//can we inflate a view here instead of assigning a drawable image?
}
}
I don't think that it is possible
What you can do is draw custom icon at runtime:
Draw it on Canvas
Generate Drawable at runtime (instead of creating xml, you can create an object. So if you were to type <shape>, you can replace it with new Shape(); in Java)
Generate a view and copy its bitmap (How to convert Views to bitmaps?) this option would provive only looks of it - things like click listeners will not work, thus I don't see a reason for choosing this option
This possible using the following utility class:
/**
* Utility class to generate Bitmaps for Symbol.
* <p>
* Bitmaps can be added to the map with {#link com.mapbox.mapboxsdk.maps.MapboxMap#addImage(String, Bitmap)}
* </p>
*/
private static class SymbolGenerator {
/**
* Generate a Bitmap from an Android SDK View.
*
* #param view the View to be drawn to a Bitmap
* #return the generated bitmap
*/
public static Bitmap generate(#NonNull View view) {
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(measureSpec, measureSpec);
int measuredWidth = view.getMeasuredWidth();
int measuredHeight = view.getMeasuredHeight();
view.layout(0, 0, measuredWidth, measuredHeight);
Bitmap bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.TRANSPARENT);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
}
A full example integrating this code can be found here. Note that this code can be executed on a background thread, so you don't need to block the main thread for it. While we don't expose a binary atm we are looking into creating a little plugin around this code. The feature request for it can be found here.
So I recently wanted to try out the caching feature of the Picasso library, & I got into this confusing situation:
I retrieve the images' file names & paths from my web server (using Retrofit2), & I store them into ImageComponent objects (model):
public class ImageComponent {
private int id; // 'id' in database
private String filename; // image name
private String path; // image path in server storage
private Bitmap bitmap;
// Overloaded constructor
// Getters & setters
}
So now that the loading is successful, I populate a RecyclerView with these images using Picasso. The loading and inflation process is successful, but it gets a little tricky when caching the images.
Case1: using android.util.LruCache
(For convenience, I will post the entire code of the Recyclerview's adapter. I will try to be concise)
// imports
import android.util.LruCache;
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder> {
private Context mContext; // Activity's context
private List<ImageComponent> mImages; // The imageComponents to display
// The contreversial, infamous cache
private LruCache<Integer, Bitmap> mImageCache;
public ImageAdapter(Context context, List<ImageComponent> images) {
mContext = context;
mImages = images;
// Provide 1/8 of available memory to the cache
final int maxMemory = (int)(Runtime.getRuntime().maxMemory() /1024);
final int cacheSize = maxMemory / 8;
mImageCache = new LruCache<>(cacheSize);
}
#Override
public ImageAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Nothing special
}
#Override
public void onBindViewHolder(final ImageAdapter.ViewHolder holder, final int position) {
// Current ImageComponent
ImageComponent imageComponent = mImages.get(position);
// Full image path in server storage
String imagePath = Constants.SERVER_IP_ADDRESS + Constants.UPLOADS_DIRECTORY
+ imageComponent.getPath();
// Display the file's name
holder.text.setText(imageComponent.getFilename());
final ImageView imageView = holder.image;
// Get bitmap from cache, check if it exists or not
Bitmap bitmap = mImageCache.get(imageComponent.getId());
if (bitmap != null) {
Log.i("ADAPTER", "BITMAP IS NOT NULL - ID = " + imageComponent.getId());
// Image does exist in cache
holder.image.setImageBitmap(imageComponent.getBitmap());
}
else {
Log.i("ADAPTER", "BITMAP IS NULL");
// Callback to retrieve image, cache it & display it
final Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
ImageComponent img = mImages.get(position);
// Display image
holder.image.setImageBitmap(bitmap);
// Cache the image
img.setBitmap(bitmap);
mImages.set(position, img);
mImageCache.put(img.getId(), bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
// Tag the target to the view, to keep a strong reference to it
imageView.setTag(target);
// Magic
Picasso.with(mContext)
.load(imagePath)
.into(target);
}
}
#Override
public int getItemCount() {
return mImages.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView image;
TextView text;
// Constructor & view binding, not that special
}
}
RESULT1
(Notice those 2 last images, & how they show other previous images before displaying the correct one)
A few notes:
I ran across a problem, where the images weren't displayed at all. After some research, I found this answer which suggested binding the target to the ImageView. (worked)
I didn't quite understand how Picasso caches the images. Is it an automatic or manual process ? This answer states that Picasso handles this task for you. But when I actually tried it out (without the android Lrucache), no caching seemed to be done : The images were getting reloaded every time I scroll back & forth.
Actually I was going to post a second use case where things went even more wrong, using the Picasso's Lrucache (images were being shown randomly , & change with every scroll), but I think this post is already long enough.
My questions are:
Why do I get that weird behavior ? (as shown in the attached GIF)
How does this whole caching process work ? Should I (or could I) use a Lrucache when making use of Picasso ?
What's the difference between the Lrucache that comes with the SDK & Picasso's ? (Performance, best use case scenarios, etc...)
I think using both LRU cache and Picasso is causing the weird behaviour. I have used Picasso to cache Image to an Adapter, which works completely fine. you can check in here
Picasso cache Image automatically when used with adapter, it will cache like this, if the child item of list/Recycler view is not visible it will stop caching the image for the respective child.So it's better to use Picasso alone with Adapter.
The main usage of Picasso over LRU cache is that, Picasso is easy to use.
ex : specifying Memory cache Size in Picasso.
Picasso picasso = new Picasso.Builder(context)
.memoryCache(new LruCache(250))
.build();
Picasso also allow you to notify user with an Image when there is an error in downloading, a default holder for Imageview before loading the complete image.
Hope it helps.
I use the following code to initialize my bitmap variable:
Bitmap bitmap = ((BitmapDrawable)this.model.gameView.context.getResources().getDrawable(R.drawable.pic1)).getBitmap();
When I try to log the width of that bitmap, the log does not even output anything for that call.
I know it's making it to that line, because I traced the code.
Also, when I try to do canvas.draw for the bitmap, nothing is drawn on the screen.
Everything I draw with rectangles works fine.
My picture resource is a PNG.
Any help is appreciated!
Try something like this for your bitmap class.
public class DrawBitmap extends View
{
Bitmap bitmap;
public DrawBitmap(Context content)
{
super(content);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic1);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);//whatever color you want, make sure it's not the same as your image
canvas.drawBitmap(bitmap, (canvas.getWidth()), 0, null);
}
}
Main Class
public class Main extends Activity
{
DrawBitmap myView;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myView = new DrawBitmap(this);
setContentView(myView);
}
}
Try using BitmapFactory.decodeResource
Have a look at the answer in this topic:
How to convert a Drawable to a Bitmap?
Just figured it out. It had nothing to do with the method of bitmap loading I used.
It was a logical error on my part. My code accidentally reached a case where my bitmap became null, and it tried to draw the null resource on the canvas.
First of all, thanks in advance for any help you can provide. This has been driving me nuts and google's been no friend of mine for this.
I've created a DrawView so the user can paint something in the app. What I'd like to do is place that DrawView within a layout XML file, so I can place a TextView title on the top and two buttons on the bottom, one of which will save what the user painted as an image file. So far, all I've been able to do is create an area for the user to paint in. Is there any way to place this DrawView view into an XML file? If not, what alternative solutions do I have?
Also, this is an optional question as I'm sure I'll find the solution by googling, at some point, but is there a better way to smoothly draw along the user's finger movement, other than the canvas.drawLine() method I am currently using?
public class Draw extends Activity {
DrawView drawView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set full screen view
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
requestWindowFeature(Window.FEATURE_NO_TITLE);
drawView = new DrawView(this);
setContentView(drawView);
drawView.requestFocus();
}
}
public class DrawView extends View implements OnTouchListener {
private static final String TAG = "DrawView";
ArrayList <Float> nikpointx = new ArrayList<Float>();
ArrayList <Float> nikpointy = new ArrayList<Float>();
Bitmap nikbmp;
Paint paint = new Paint();
public DrawView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
paint.setStrokeWidth(4);
}
#Override
public void onDraw(Canvas canvas) {
//I'm doing this, because the drawCircle method places circles at different points
//in the screen, rather than drawing smooth lines. I haven't found a more elegant
//solution yet :-(
canvas.drawLine(nikpointx.get(i-1), nikpointy.get(i-1), nikpointx.get(i), nikpointy.get(i), paint);
canvas.setBitmap(nikbmp);
}
}
public boolean onTouch(View view, MotionEvent event) {
// if(event.getAction() != MotionEvent.ACTION_DOWN)
// return super.onTouchEvent(event);
Point point = new Point();
point.x = event.getX();
point.y = event.getY();
points.add(point);
invalidate();
Log.d(TAG, "point: " + point);
nikpointx.add(event.getX());
nikpointy.add(event.getY());
return true;
}
}
You can place you custom view in the xml layout like this:
<full.package.to.customview.DrawView android:id="#+id/idOfCustom"
//other attributes
/>
You can also add custom attributes to your view to set directly from the xml file.
I don't have any solutions for your second problem.
edit: after a quick search on this site i found this -> Android How to draw a smooth line following your finger