I need a bit of help. I have an ImageView with a touch listener, and I am able to capture all touch inputs into a matrix, and apply that matrix to the ImageView and VOILA! the image pans and zooms appropriately.
Here is the trouble: I'd now like to CROP the image in such a way that it ALWAYS ends up the same size; eg a 300x300 image.
In other words, suppose I have a 300x300 square in the middle of my screen, a user pans and zooms an image until an item of interest fits into that square, and hits "next". I would like to have a resulting image that has cropped the photo to only be the 300x300 portion that was contained in the box.
Make sense?? Please help! Thanks comrades! See a bit of code below for what I have tried thus far.
float[] matrixVals = new float[9];
ImageTouchListener.persistedMatrix.getValues(matrixVals);
model.setCurrentBitmap(Bitmap.createBitmap(model.getOriginalBitmap(), 0, 0, model.getTargetWidth(), model.getTargetHeight(), ImageTouchListener.persistedMatrix, true));
model.setCurrentBitmap(Bitmap.createBitmap(model.getCurrentBitmap(), Math.round(matrixVals[Matrix.MTRANS_X]), Math.round(matrixVals[Matrix.MTRANS_Y]), model.getTargetWidth(), model.getTargetHeight(), null, false));
Finally, I would also like to be able to SHRINK the image into the box, where the edges may actually need to be filled in with black or white or some kind of border... So far, everything I do other than no pan or zoom at all crashes when I hit next.
Thanks again!
see this custom ImageView, the most important part is onTouchEvent where cropped Bitmap is created and saved to /sdcard for verification:
class IV extends ImageView {
Paint paint = new Paint();
Rect crop = new Rect();
public IV(Context context) {
super(context);
paint.setColor(0x660000ff);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
crop.set(w / 2, h / 2, w / 2, h / 2);
crop.inset(-75, -75);
}
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
setScaleType(ScaleType.MATRIX);
Matrix m = getImageMatrix();
m.postScale(2, 2);
m.postTranslate(40, 30);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Bitmap croppedBitmap = Bitmap.createBitmap(crop.width(), crop.height(), Config.ARGB_8888);
Canvas c = new Canvas(croppedBitmap);
c.translate(-crop.left, -crop.top);
c.concat(getImageMatrix());
getDrawable().draw(c);
// just save it for test verification
try {
OutputStream stream = new FileOutputStream("/sdcard/test.png");
croppedBitmap.compress(CompressFormat.PNG, 100, stream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return false;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(crop, paint);
}
}
It's not really clear for me what your problem is, a calculation or a drawing problem ... if it is a drawing problem I might have the solution ...
Create a new bitmap, get the canvas and draw the correct rect of the big image into the new smaller one ....
Bitmap bigPicture; //your big picture
Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
Rect source = ...
Rect dest = ...
c.drawBitmap(bigPicture, source, dest, paint);
Now if your problem is a calculation problem I might have a solution too ...
Related
Path drawn on scaled GLES20RecordingCanvas has quality as if it was drawn unscaled in bitmap and then up-scaled.
In contrast, if I create Canvas with backing bitmap and then apply the same scaling transformations to Canvas object I get much superior bitmap.
Here both circles are drawn with Path.addCircle and using Canvas.scale. Upper circle is drawn with scaled GLES20RecordingCanvas and lower is drawn with scaled simple Canvas with backing bitmap.
Some code:
public class TestPathRenderer extends View {
...
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
float distortedWidth = getDistortedWidth();
float distortedHeight = getDistortedHeight();
path.reset();
path.addCircle(distortedWidth/2f, distortedHeight/2f, Math.min(distortedWidth/2f, distortedHeight/2f), Path.Direction.CW);
bitmap = assembleNewBitmap(measuredWidth, measuredHeight);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
switch (renderMode) {
case RENDER_MODE_WO_BITMAP:
drawOnCanvas(canvas);
break;
case RENDER_MODE_WITH_BITMAP:
canvas.drawBitmap(bitmap, 0f, 0f, paint);
break;
default:
throw new UnsupportedOperationException("Undefined render mode: " + renderMode);
}
}
private Bitmap assembleNewBitmap(int w, int h) {
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawOnCanvas(canvas);
return bitmap;
}
private void drawOnCanvas(#NonNull Canvas canvas) {
canvas.save();
canvas.scale(DISTORTION_FACTOR, DISTORTION_FACTOR);
canvas.drawPath(path, paint);
canvas.restore();
}
}
Full example
I can't understand the quality difference with these two cases. For me it seems that they have to be interchangeable.
Poor quality during scaling is a limitation of Hardware Acceleration according to Android docs.
After seeing your question, I decided to check out the source code of the GLES20RecordingCanvas class. And here's what I found out:
GLES20RecordingCanvas extends from GLES20Canvas which extends from HardwareCanvas. This HardwareCanvas class extends from Canvas. But the main difference I noticed is that it overrides the isHardwareAcceleratedMethod() returning true.
So my assumption is that the GLES20RecordingCanvas renders the bitmap with Hardware Acceleration while Canvas doesn't. And this is probably why you get less quality with the GLES20RecorgingCanvas.
I created a function to draw a text on a path:
public void drawText(float x, float y, String text) {
Log.i("DRAWING", "drawText");Typeface.BOLD);
mPath.reset();
mPath.moveTo(x, y);
mPath.lineTo(x+200,y);
Paint textPaint = new Paint();
textPaint.setColor(Color.RED);
textPaint.setTextSize(20f);
textPaint.setAntiAlias(true);
textPaint.setStrokeWidth(5f);
textPaint.setStyle(Paint.Style.FILL);
mCanvas.drawTextOnPath(text, mPath, 0, 0, textPaint);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
paths.add(mPath);
invalidate();
}
I set a Bitmap like this:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas();
mCanvas.setBitmap(mBitmap);
reset();
}
Question:
When I set a bitmap, it works fine and the text appears on the ImageView, but when I don't, just a white line appears and not the text.
Do I have to use a Bitmap to draw text on path with drawTextOnPath ? Because I want to use only paths (all works fine except text, like it needed a Bitmap).
So a Bitmap is mandatory to draw text with drawTextOnPath.
The text won't appear if there is no Bitmap.
For those who, like me, implemented a Undo/Redo functionnality, I guess you have to save both paths and bitmaps to allow user to go back and forward.
So, recently me and my two teammates started on our exam project. Which is a basic 2D skijumping game.
We've done programming on android before but never in relation to games and we have the basic techniques down for creating animations using a thread and all that. But we seem to have a scaling issue.
Currently we have a background image in the drawable folder in the resolution 1920x1080, we are using BitmapFactory to scale the background image to the users screen size, which seems to be working fine it fits perfectly on all our 3 different smartphones.
Were creating an animation of the player sprite skiing down the hill and jumping off the edge, which looks very funny and works perfectly. For this I simply divided the screen width and height by 100 and used that percentage of either 1% screen width to move him at certain speeds on the x-axis and likewise on the y-axis with the 1% of the screen height.
It works on most phones but, for some reason it won't work on all of them? I don't understand why not? I mean the math should always work shouldn't it? On some phones he skies outside of the hill in the air and not on the proper trajectory. I don't get what I'm missing but I'm suspecting it has something to do with the screen format or something?
Can anyone enlighten me on this? Please keep in mind this is our first android game so were new at game dev.
And thanks for all your input if you have any in advance.
try this:
class V extends View implements Callback {
private Bitmap mBitmap;
private Matrix mMatrix;
private Handler mHandler;
private float mX;
private Paint mPaint;
public V(Context context) {
super(context);
Resources res = getResources();
mBitmap = BitmapFactory.decodeResource(res, R.drawable.layer0);
mMatrix = new Matrix();
mHandler = new Handler(this);
mHandler.obtainMessage(0).sendToTarget();
mPaint = new Paint();
mPaint.setColor(0xff00ff00);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
RectF src = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
RectF dst = new RectF(0, 0, w, h);
mMatrix.setRectToRect(src, dst, ScaleToFit.CENTER);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.concat(mMatrix);
canvas.drawBitmap(mBitmap, 0, 0, null);
canvas.drawRect(mX, 20, mX + 20, 40, mPaint);
}
#Override
public boolean handleMessage(Message msg) {
msg = mHandler.obtainMessage(0);
if (mX < mBitmap.getWidth()) {
mX += 1.5f;
mHandler.sendMessageDelayed(msg, 50);
} else {
Log.d(TAG, "handleMessage stop");
}
invalidate();
return true;
}
}
I got a picture of a sun, 860x860 pixels.
I want to rotate the sun with the anchor-point being center of the screen.
This is what I got so far:
class GraphicalMenu extends View{
int screenH;
int screenW;
int angle;
Bitmap sun, monster;
public GraphicalMenu(Context context){
super(context);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
sun = BitmapFactory.decodeResource(getResources(),R.drawable.sun,options);
monster = BitmapFactory.decodeResource(getResources(),R.drawable.monster,options);
}
#Override
public void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
screenH = h;
screenW = w;
sun = Bitmap.createScaledBitmap(sun, w, h, true);
monster = Bitmap.createScaledBitmap(monster, w, h, true);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Increase rotating angle.
if (angle++ >360)
angle =0;
Matrix matrix = new Matrix();
matrix.setRotate(angle , getWidth()/2, getHeight()/2);
canvas.drawBitmap(sun, matrix, new Paint());
//Call the next frame.
canvas.drawBitmap(monster,0 , 0, null);
invalidate();
}
}
I've tried to change this line:
sun = Bitmap.createScaledBitmap(sun, w, h, true);
to:
sun = Bitmap.createScaledBitmap(sun, h, h, true);
but then the sun leaves the center of the screen and rotates far out to the right.
How can I fit the sun to the screen?
And how can I make it keep its ratio?
edit
Screenshot from running it on my N5 and the picture of the sun.
If I understand correctly, you have several problems with your code:
If you're making multiple calls to onSizeChanged() then you'll want to keep the original bitmap (pre-scaled) otherwise when you upscale the bitmaps again, they will look pixelated because every time you downscale them, you're losing information.
When you provide a matrix through which to transform your bitmap for drawing onto the canvas, that matrix will apply to the bitmap itself, not the screen as a whole. So when you make the drawBitmap() call, what you are actually requesting is for the bitmap to be rotated around the local point (getWidth()/2, getHeight()/2), not the screen point.
You're not actually rotating around the screen centre, you're rotating around the View's centre. Unless the View takes up the entire screen, you won't get the intended effect.
I can't pinpoint exactly what your problem is, but uploading some images would likely clarify what you're trying to achieve and what your current situation is.
I have a drawing app that allows the user to draw to a blank canvas. I am attempting to draw a scaled 'thumbnail' of the current bitmap so that when the user has scaled in the View, they can reference the thumbnail to get a sense as to where they are in the overall draw canvas. I have the scaling working, and am displaying the thumbnail in the correct location, but it appears that the thumbnail is not being updated on subsequent onDraws when new lines/shapes are added.
So that I have access to the underlying bitmap for this view (to show the thumbnail, be able to easily save the bitmap to a file, etc) I do the following in onSizeChanged() for the View:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// set the canvas height, panning, etc, based on the now inflated View
mWidth = getWidth();
mHeight = getHeight();
mAspectRatio = mWidth / mHeight;
mPanX = 0;
mPanY = 0;
// create the Bitmap, set it to the canvas
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBitmap);
draw(mCanvas);
}
Then, when the user draws and invalidate() is called, I do the following in onDraw() to generate the thumbnail:
#Override
protected void onDraw(Canvas canvas) {
<snipped code that draws paths, shapes to canvas>
if (mScaled) {
Bitmap out = Bitmap.createScaledBitmap(mBitmap, (int) thumbWidth, (int) thumbHeight, false);
canvas.drawBitmap(out, null, thumbnailRectF, thumbCanvasPaint);
}
}
The thumbnail gets displayed in the space defined by thumbnailRectF using the thumbCanvasPaint, but in subsequent onDraw() calls, the scaled bitmap has not changed from it's original state, even though the full-sized active canvas shows all of the drawings, etc. Based on some testing, it seems to me that while I am setting the Bitmap with the initial call to draw(mCanvas);, subsequent onDraws are writing to the underlying Bitmap rather than the one specified in onSizeChanged().
So, I guess I am trying to figure out how I tie the onDraw canvas to a Bitmap that I can readliy access to perform re-sizes, save, etc. Looking at this question, I thought that the draw(mCanvas); call would tie the onDraw to the bitmap specified in the mCanvas (in my case, mBitmap), but in practice, it doesn't seem to be working, in so far as updats to the canvas are concerned.
Thanks,
Paul
canvas.drawBitmap(out, null, thumbnailRectF, thumbCanvasPaint);
should change to
canvas.drawBitmap(out, new Rect(0,0,mBitmap.getWidht, mBitmap.getheight), thumbnailRectF, thumbCanvasPaint);
There is no need for
Bitmap out = Bitmap.createScaledBitmap(mBitmap, (int) thumbWidth, (int)....
Also check that mScaled is true all the time when zoom is greater than 1
Scale bitmap by Bitmap.createScaledBitmap then draw will not work
The solution for scale the canvas bitmap is use this function (from the docs)
void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
// dst : Rect: The rectangle that the bitmap will be scaled/translated to fit into
so by changing the size of dst, your bitmap size will change
Here is example if I want to draw a bitmap at top-left and scale it to 100px x 120px
Bitmap bitmap = BitmapFactory.decodeResource(...);//bitmap to be drawn
float left = 0;
float top = 0;
RectF dst = new RectF(left, top, left + 100, top + 120); // width=100, height=120
canvas.drawBitmap(bitmap, null, dst, null);