I'm trying to dynamically create images in android by taking an existing Bitmap and removing the centre of it in order to make a "cropped" version. The resulting image's height would naturally be smaller than the original, something like the attached example.
I've got a rough way of doing this by creating two new Bitmaps from the original, one containing the top of the image above the crop section (e.g. the android's head in the example) and the other containing the remaining image below the crop section (the android's feet) using the Bitmap.createBitmap(source, x, y, width, height) method, then drawing both of these bitmaps onto a canvas of a size equal to the original image minus the removed space.
This feels a bit clunky, and as I could be calling this method several times a second, it seems wasteful to create two bitmaps each time.
I was wondering if there was a more efficient way of doing this. Something like drawing the original Bitmap onto a canvas using a Path with it's Paint's xfermode set to a
new PorterDuffXfermode(Mode.DST_OUT) in order to cut out the portion of the image I wish to delete. But this seems to clear that area and not shrink the image down i.e. it leaves a big empty gap in the Android's middle.
Any suggestions greatly appreciated!
Why do you create two bitmaps? You only need to create one bitmap and then do canvas.drawBitmap() twice.
Bitmap bmpOriginal;
Bitmap bmpDerived = Bitmap.create(...);
Canvas canvas = new Canvas(bmpDerived);
canvas.drawBitmap(bmpOriginal, rectTopSrc, rectTopDst, null);
canvas.drawBitmap(bmpOriginal, rectBottomSrc, rectBottomDst, null);
Done.
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);
I'm not sure I'm doing this the "right" way, so I'm open to other options as well. Here's what I'm trying to accomplish:
I want a view which contains a graph. The graph should be dynamically created by the app itself. The graph should be zoom-able, and will probably start out larger than the screen (800x600 or so)
I'm planning on starting out simple, just a scatter plot. Eventually, I want a scatter plot with a fit line and error bars with axis that stay on the screen while the graph is zoomed ... so that probably means three images overlaid with zoom functions tied together.
I've already built a view that can take a drawable, can use focused pinch-zoom and drag, can auto-scale images, can switch images dynamically, and takes images larger than the screen. Tying the images together shouldn't be an issue.
I can't, however, figure out how to dynamically draw simple images.
For instance: Do I get a BitMap object and draw on it pixel by pixel? I wanted to work with some of the ShapeDrawables, but it seems they can only draw a shape onto a canvas ... how then do I get a bitmap of all those shapes into my view? Or alternately, do I have to dynamically redraw /all/ of the image I want to portray in the "onDraw" routine of my view every time it moves or zooms?
I think the "perfect" solution would be to use the ShapeDrawable (or something like it to draw lines and label them) to draw the axis with the onDraw method of the view ... keep them current and at the right level ... then overlay a pre-produced image of the data points / fit curve / etc that can be zoomed and moved. That should be possible with white set to an alpha on the graph image.
PS: The graph image shouldn't actually /change/ while on the view. It's just zooming and being dragged. The axis will probably actually change with movement. So pre-producing the graph before (or immediately upon) entering the view would be optimal. But I've also noticed that scaling works really well with vector images ... which also sounds appropriate (rather than a bitmap?).
So I'm looking for some general guidance. Tried reading up on the BitMap, ShapeDrawable, Drawable, etc classes and just can't seem to find the right fit. That makes me think I'm barking up the wrong tree and someone with some more experience can point me in the right direction. Hopefully I didn't waste my time building the zoom-able view I put together yesterday :).
First off, it is never a waste of time writing code if you learned something from it. :-)
There is unfortunately still no support for drawing vector images in Android. So bitmap is what you get.
I think the bit you are missing is that you can create a Canvas any time you want to draw on a bitmap. You don't have to wait for onDraw to give you one.
So at some point (from onCreate, when data changes etc), create your own Bitmap of whatever size you want.
Here is some psuedo code (not tested)
Bitmap mGraph;
void init() {
// look at Bitmap.Config to determine config type
mGraph = new Bitmap(width, height, config);
Canvas c = new Canvas(mybits);
// use Canvas draw routines to draw your graph
}
// Then in onDraw you can draw to the on screen Canvas from your bitmap.
protected void onDraw(Canvas canvas) {
Rect dstRect = new Rect(0,0,viewWidth, viewHeight);
Rect sourceRect = new Rect();
// do something creative here to pick the source rect from your graph bitmap
// based on zoom and pan
sourceRect.set(10,10,100,100);
// draw to the screen
canvas.drawBitmap(mGraph, sourceRect, dstRect, graphPaint);
}
Hope that helps a bit.
I have a widget which looks like this:
Every cone is a "touchable element". Now I want to put a bitmap texture above each cone. However, bitmap images are all rectangular, so a bitmap texture above one cone would interfere with the bitmap texture above another cone.
I'm wondering what is the best solution to this approach. Should I just create an image which fits (as a rectangle) exactly above the cone and make the non used areas transparent?
A second question is, how do bitmap textures work with stretching? Because this whole circle draws itself to fit the whole screen size, while bitmap textures are pretty much one size.
Offhand I can't think of a better way to draw bitmaps over those cones than your own suggestion of using transparent zones.
However, I can help with your second question as stretching bitmaps is not hard. You've got a few options in the Canvas class. For example:
canvas.save();
canvas.scale(xRatio, yRatio);
canvas.drawBitmap(....);
canvas.restore();
You can also use a Matrix, using matrix.postScale(xRatio, yRatio), to then generate either a larger bitmap and draw it normally, or pass the matrix in to your canvas.drawBitmap(....) command to make it scale while it draws.
All of these methods assume you have access to the drawing canvas itself. If you are using a view, you can subclass it and override the onDraw(Canvas canvas) method to grab the canvas before it starts drawing it. If you're using a SurfaceHolder, then you should already know how to get the canvas.
Edit: I forgot the third method I was going to describe. You can use the canvas.drawBitmap(bitmap, srcRect, dstRect, paint) to also make the canvas scale the bitmap to fit the destination rectangle. In short, there are lots of methods to do this - pick the one that's easiest based on your application!
Say I have a somewhat large (i.e. not fit in most phones' memory) bitmap on disk. I want to draw only parts of it on the screen in a way that isn't scaled (i.e. inSampleSize == 1)
Is there a way to load/draw just the part I want given a Rect specifying the area without loading the entire bitmap content?
I'm quite confident this is possible since you can load a really large bitmap file into an ImageView without problems so there must be some sort of a built-in way to handle large bitmaps... and after a few attempts, I've found a solution:
Instead of loading the entire bitmap and manually draw it yourself, load it as a Drawable instead:
InputStream mapInput = getResources().openRawResource(
R.drawable.transit_map);
_map = Drawable.createFromStream(mapInput, "transit_map");
_map.setBounds(0, 0, _mapDimension.width(), _mapDimension.height());
I'm using a resource file but since you can use Drawable.createFromStream to load image from any InputStream, it should works with arbitrary bitmap.
Then, use the Drawable.draw method to draw it onto the desired canvas like so:
int left = -(int) contentOffset.x;
int top = -(int) contentOffset.y;
int right = (int) (zoom * _mapDimension.width() - contentOffset.x);
int bottom = (int) (zoom * _mapDimension.height() - contentOffset.y);
_map.setBounds(left, top, right, bottom);
_map.draw(canvas);
As in the above case, You can also scale and translate the bitmap as well by manipulating the drawable's bounds and only the relevant parts of the bitmap will be loaded and drawn onto the Canvas.
The result is a pinch-zoomable view from just one single 200KB bitmap file. I've also tested this with a 22MB PNG file and it still works without any OutOfMemoryError including when screen orientation changes.
Now it's very relevant: BitmapRegionDecoder.
Note: available since Android SDK 10
It can easily be done by using RapidDecoder.
import rapid.decoder.BitmapDecoder;
Rect bounds = new Rect(10, 20, 30, 40);
Bitmap bitmap = BitmapDecoder.from("your-file.png")
.region(bounds)
.decode();
imageView.setImageBitmap(bitmap);
It supports down to Android 2.2 (API Level 8).
Generally speaking, that isn't possible, particularly since most image formats are compressed, so you don't even know which bytes to read until you've extracted the uncompressed form.
Break your image up into small tiles and load just the tiles you need to cover the region you want to display at runtime. To avoid jittery scrolling, you might also want to preload tiles that are just out of sight (the ones that border the visible tiles) on a background thread.
I have a class that extends View. I override the onDraw method and allow the user to draw on the screen. I am at the point where I want to save this view as an image. I Can use buildDrawingCache and getDrawingCache to create a bitmap that I can write to the SD card. However, the image is not good quality at a large size, it has jagged edges. Since I have a View and I use Paths I can transform all by drawing to a bigger size. I just don't know how to make the Canvas bigger so when I call getDrawingCache it doesn't crop all the paths I am just transformed. What is happening is I transform all my paths but when I write the Bitmap to file I am only getting the "viewport" of the actual screen size. I want something much bigger. Any help in the right direction would be greatly appreciated. I have been reading the docs and books and am at a loss.
Thanks
Jon
You can create another Canvas backed by a Bitmap that you create and draw all the paths to that. Then the Bitmap will hold the larger, higher resolution image. It would look something like this:
Bitmap largeBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas largeCanvas = new Canvas(largeBitmap);
// Draw the paths to this canvas and then use largeBitmap to save to a file.