I'm new to the Glide library, following the Transformations guide found here: https://github.com/bumptech/glide/wiki/Transformations
I'm trying to create a custom transformation, but when I place a breakline in the Transformation class's transform method, I can see that it is never called.
Below is my code:
private static class CustomTransformation extends BitmapTransformation {
private Context aContext;
public CustomTransformation(Context context) {
super(context);
aContext = context;
}
#Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return bitmapChanger(toTransform, 1080, (int) aContext.getResources().getDimension(R.dimen.big_image));
}
#Override
public String getId() {
return "some_id";
}
}
private static Bitmap bitmapChanger(Bitmap bitmap, int desiredWidth, int desiredHeight) {
float originalWidth = bitmap.getWidth();
float originalHeight = bitmap.getHeight();
float scaleX = desiredWidth / originalWidth;
float scaleY = desiredHeight / originalHeight;
//Use the larger of the two scales to maintain aspect ratio
float scale = Math.max(scaleX, scaleY);
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
//If the scaleY is greater, we need to center the image
if(scaleX < scaleY) {
float tx = (scale * originalWidth - desiredWidth) / 2f;
matrix.postTranslate(-tx, 0f);
}
return Bitmap.createBitmap(bitmap, 0, 0, (int) originalWidth, (int) originalHeight, matrix, true);
}
I've tried initiating Glide in two ways:
Glide.with(this).load(url).asBitmap().transform(new CustomTransformation(this)).into(imageView);
and
Glide.with(this).load(url).bitmapTransform(new CustomTransformation(this)).into(imageView);
But neither work. Any ideas? Again, I'm not looking for advice on the Matrix itself, I just don't understand why transform(...) isn't being called at all. Thanks!
You're most likely experiencing caching issues. The first time you compiled and executed your code the result of the transformation was cached so next time it doesn't have to be applied to the same source image.
Each transformation has a getId() method which is used in determining whether the transformation result has changed. Usually transformations don't change, but are either applied or not. You can change it on every build while developing, but it could be tedius.
To work around this problem you can add the following two calls to your Glide load line:
// TODO remove after transformation is done
.diskCacheStrategy(SOURCE) // override default RESULT cache and apply transform always
.skipMemoryCache(true) // do not reuse the transformed result while running
The first one can be changed to NONE, but then you would have to wait for the url to load from the internet every time, instead of just reading the image from the phone. The second one is useful if you have can navigate to and away from the transformation in question and want to debug it for example. It helps to not need a restart after every load to clear the memory cache.
Don't forget to remove these after you're done with the Transformation's development, because they affect production performance a lot and should be used after much consideration only, if at all.
Note
It looks like you're trying to resize your image to a certain size before loading, you can use .override(width, height) in combination with .centerCrop()/.fitCenter()/.dontTransform() for that.
Related
I want to make a waveform drawing for an audio recorder in Android. The usual one with lines/bars, like this one:
More importantly, I want it live, while the song is being recorded. My app already computes the RMS through AudioRecord. But I am not sure which is the best approach for the actual drawing in terms of processing, resources, battery, etc.
The Visualizer does not show anything meaningful, IMO (are those graphs more or less random stuff??).
I've seen the canvas approach and the layout approach (there are probably more?). In the layout approach you add thin vertical layouts in a horizontal layout. The advantage is that you don't need to redraw the whole thing each 1/n secs, you just add one layout each 1/n secs... but you need hundreds of layouts (depending on n). In the canvas layout, you need to redraw the whole thing (right??) n times per second. Some even create bitmaps for each drawing...
So, which is cheaper, and why? Is there anything better nowadays? How much frequency update (i.e., n) is too much for generic low end devices?
EDIT1
Thanks to the beautiful trick #cactustictacs taught me in his answer, I was able to implement this with ease. Yet, the image is strangely rendered kind of "blurry by movement":
The waveform runs from right to left. You can easily see the blur movement, and the left-most and right-most pixels get "contaminated" by the other end. I guess I can just cut both extremes...
This renders better if I make my Bitmap bigger (i.e., making widthBitmap bigger), but then the onDraw will be heavier...
This is my full code:
package com.floritfoto.apps.ave;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import java.util.Arrays;
public class Waveform extends androidx.appcompat.widget.AppCompatImageView {
//private float lastPosition = 0.5f; // 0.5 for drawLine method, 0 for the others
private int lastPosition = 0;
private final int widthBitmap = 50;
private final int heightBitmap = 80;
private final int[] transpixels = new int[heightBitmap];
private final int[] whitepixels = new int[heightBitmap];
//private float top, bot; // float for drawLine method, int for the others
private int aux, top;
//private float lpf;
private int width = widthBitmap;
private float proportionW = (float) (width/widthBitmap);
Boolean firstLoopIsFinished = false;
Bitmap MyBitmap = Bitmap.createBitmap(widthBitmap, heightBitmap, Bitmap.Config.ARGB_8888);
//Canvas canvasB = new Canvas(MyBitmap);
Paint MyPaint = new Paint();
Paint MyPaintTrans = new Paint();
Rect rectLbit, rectRbit, rectLdest, rectRdest;
public Waveform(Context context, AttributeSet attrs) {
super(context, attrs);
MyPaint.setColor(0xffFFFFFF);
MyPaint.setStrokeWidth(1);
MyPaintTrans.setColor(0xFF202020);
MyPaintTrans.setStrokeWidth(1);
Arrays.fill(transpixels, 0xFF202020);
Arrays.fill(whitepixels, 0xFFFFFFFF);
}
public void drawNewBar() {
// For drawRect or drawLine
/*
top = ((1.0f - Register.tone) * heightBitmap / 2.0f);
bot = ((1.0f + Register.tone) * heightBitmap / 2.0f);
// Using drawRect
//if (firstLoopIsFinished) canvasB.drawRect(lastPosition, 0, lastPosition+1, heightBitmap, MyPaintTrans); // Delete last stuff
//canvasB.drawRect(lastPosition, top, lastPosition+1, bot, MyPaint);
// Using drawLine
if (firstLoopIsFinished) canvasB.drawLine(lastPosition, 0, lastPosition, heightBitmap, MyPaintTrans); // Delete previous stuff
canvasB.drawLine(lastPosition ,top, lastPosition, bot, MyPaint);
*/
// Using setPixel (no tiene sentido, mucho mejor setPixels.
/*
int top = (int) ((1.0f - Register.tone) * heightBitmap / 2.0f);
int bot = (int) ((1.0f + Register.tone) * heightBitmap / 2.0f);
if (firstLoopIsFinished) {
for (int i = 0; i < top; ++i) {
MyBitmap.setPixel(lastPosition, i, 0xFF202020);
MyBitmap.setPixel(lastPosition, heightBitmap - i-1, 0xFF202020);
}
}
for (int i = top ; i < bot ; ++i) {
MyBitmap.setPixel(lastPosition,i,0xffFFFFFF);
}
//System.out.println("############## "+top+" "+bot);
*/
// Using setPixels. Works!!
top = (int) ((1.0f - Register.tone) * heightBitmap / 2.0f);
if (firstLoopIsFinished)
MyBitmap.setPixels(transpixels,0,1,lastPosition,0,1,heightBitmap);
MyBitmap.setPixels(whitepixels, top,1, lastPosition, top,1,heightBitmap-2*top);
lastPosition++;
aux = (int) (width - proportionW * (lastPosition));
rectLbit.right = lastPosition;
rectRbit.left = lastPosition;
rectLdest.right = aux;
rectRdest.left = aux;
if (lastPosition >= widthBitmap) { firstLoopIsFinished = true; lastPosition = 0; }
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
proportionW = (float) width/widthBitmap;
rectLbit = new Rect(0, 0, widthBitmap, heightBitmap);
rectRbit = new Rect(0, 0, widthBitmap, heightBitmap);
rectLdest = new Rect(0, 0, width, h);
rectRdest = new Rect(0, 0, width, h);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawNewBar();
canvas.drawBitmap(MyBitmap, rectLbit, rectRdest, MyPaint);
canvas.drawBitmap(MyBitmap, rectRbit, rectLdest, MyPaint);
}
}
EDIT2
I was able to prevent the blurring just using null as Paint in the canvas.drawBitmap:
canvas.drawBitmap(MyBitmap, rectLbit, rectRdest, null);
canvas.drawBitmap(MyBitmap, rectRbit, rectLdest, null);
No Paints needed.
Your basic custom view approach would be to implement onDraw and redraw your current data each frame. You'd probably keep some kind of circular Buffer holding your most recent n amplitude values, so each frame you'd iterate over those, and use drawRect to draw the bars (you'd calculate things like width, height scaling, start and end positions etc in onSizeChanged, and use those values when defining the coordinates for the Rects).
That in itself might be fine! The only way you can really tell how expensive draw calls are is to benchmark them, so you could try this approach out and see how it goes. Profile it to see how much time it takes, how much the CPU spikes etc.
There are a few things you can do to make onDraw as efficient as possible, mostly things like avoiding object allocations - so watch out for loop functions that create Iterators, and in the same way you're supposed to create a Paint once instead of creating them over and over in onDraw, you could reuse a single Rect object by setting its coordinates for each bar you need to draw.
Another approach you could try is creating a working Bitmap in your custom view, which you control, and calling drawBitmap inside onDraw to paint it onto the Canvas. That should be a pretty inexpensive call, and it can easily be stretched as required to fit the view.
The idea there, is that very time you get new data, you paint it onto the bitmap. Because of how your waveform looks (like blocks), and the fact you can scale it up, really all you need is a single vertical line of pixels for each value, right? So as the data comes in, you paint an extra line onto your already-existing bitmap, adding to the image. Instead of painting the entire waveform block by block every frame, you're just adding the new blocks.
The complication there is when you "fill" the bitmap - now you have to "shift" all the pixels to the left, dropping the oldest ones on the left side, so you can draw the new ones on the right. So you'll need a way to do that!
Another approach would be something similar to the circular buffer idea. If you don't know what that is, the idea is you take a normal buffer with a start and an end, but you treat one of the indices as your data's start point, wrap around to 0 when you hit the last index of the buffer, and stop when you hit the index you're calling your end point:
Partially filled buffer:
|start
123400
|end
Data: 1234
Full buffer:
|start
123456
|end
Data: 123456
After adding one more item:
|start
723456
|end
Data: 234567
See how once it's full, you shift the start and end one step "right", wrapping around if necessary? So you always have the most recent 6 values added. You just have to handle reading from the correct index ranges, from start -> lastIndex and then firstIndex -> end
You could do the same thing with a bitmap - start "filling" it from the left, increasing end so you can draw the next vertical line. Once it's full, start filling from the left by moving end there. When you actually draw the bitmap, instead of drawing the whole thing as-is (723456) you draw it in two parts (23456 then 7). Make sense? When you draw a bitmap to the canvas, there's a call that takes a source Rect and a destination one, so you can draw it in two chunks.
You could always redraw the bitmap from scratch each frame (clear it and draw the vertical lines), so you're basically redrawing your whole data buffer each time. Probably still faster than the drawRect approach for each value, but honestly not much easier than the "treat the bitmap as another circular buffer" method. If you're already managing one circular buffer, it's not much more work - since the buffer and the bitmap will have the same number of values (horizontal pixels in the bitmap's case) you can use the same start and end values for both
You would never do this with layouts. Layouts are for premade components. They're high level combinations of components and you don't want to dynamically add or remove views from it frequently. For this, you use a custom view with a canvas. Layouts aren't even an option for something like this.
I am developing an app which is have loading large bitmap into ImageView. here i am using Bi linear algorithm for re-sizing image and it is working fine but it is taking too much time to resize(Bi-linear algorithm). i am adding some code below.
here is the code :
mutableBitmap = biLinear(mutableBitmap, outWidth,outHeight);// Bi-Linear code
mutableBitmap = Bitmap.createBitmap(mutableBitmap, 0, 0, mutableBitmap.getWidth(),
mutableBitmap.getHeight(), m, true);
Bi-Linear algorithm method :
public static Bitmap biLinear(final Bitmap input,int outWidth,int outHeight)
{
final int oldHeight=input.getHeight(),oldWidth=input.getWidth();
final int newHeight=outHeight,newWidth=outWidth;
Bitmap output =Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
// position of the top left pixel of the 4 pixels to use interpolation on
int xTopLeft,yTopLeft;
int x,y,lastTopLefty;
final float xRatio=(float)newWidth/(float)oldWidth,yratio=(float)newHeight/(float)oldHeight;
// Y color ratio to use on left and right pixels for interpolation
float ycRatio2=0,ycRatio1=0;
// pixel target in the src
float xt,yt;
// X color ratio to use on left and right pixels for interpolation
float xcRatio2=0,xcratio1=0;
int rgbTopLeft=0,rgbTopRight=0,rgbBottomLeft=0,rgbBottomRight=0,rgbTopMiddle=0,rgbBottomMiddle=0;
// do the resizing:
for(x=0;x<newWidth;x++)
{
xTopLeft=(int)(xt=x/xRatio);
// when meeting the most right edge, move left a little
if(xTopLeft>=oldWidth-1)
xTopLeft--;
if(xt<=xTopLeft+1)
{
// we are between the left and right pixel
xcratio1=xt-xTopLeft;
// color ratio in favor of the right pixel color
xcRatio2=1-xcratio1;
}
for(y=0,lastTopLefty=Integer.MIN_VALUE;y<newHeight;y++)
{
yTopLeft=(int)(yt=y/yratio);
// when meeting the most bottom edge, move up a little
if(yTopLeft>=oldHeight-1)
yTopLeft--;
// we went down only one rectangle
if(lastTopLefty==yTopLeft-1)
{
rgbTopLeft=rgbBottomLeft;
rgbTopRight=rgbBottomRight;
rgbTopMiddle=rgbBottomMiddle;
rgbBottomLeft=input.getPixel(xTopLeft,yTopLeft+1);
rgbBottomRight=input.getPixel(xTopLeft+1,yTopLeft+1);
rgbBottomMiddle=Color.argb((int)(Color.alpha(rgbBottomLeft)*xcRatio2+Color.alpha(rgbBottomRight)*xcratio1),//
(int)(Color.red(rgbBottomLeft)*xcRatio2+Color.red(rgbBottomRight)*xcratio1),//
(int)(Color.green(rgbBottomLeft)*xcRatio2+Color.green(rgbBottomRight)*xcratio1),//
(int)(Color.blue(rgbBottomLeft)*xcRatio2+Color.blue(rgbBottomRight)*xcratio1));
}
else if(lastTopLefty!=yTopLeft)
{
// we went to a totally different rectangle (happens in every loop start,and might happen more when making the picture smaller)
rgbTopLeft=input.getPixel(xTopLeft,yTopLeft);
rgbTopRight=input.getPixel(xTopLeft+1,yTopLeft);
rgbTopMiddle=Color.argb((int)(Color.alpha(rgbTopLeft)*xcRatio2+Color.alpha(rgbTopRight)*xcratio1),//
(int)(Color.red(rgbTopLeft)*xcRatio2+Color.red(rgbTopRight)*xcratio1),//
(int)(Color.green(rgbTopLeft)*xcRatio2+Color.green(rgbTopRight)*xcratio1),//
(int)(Color.blue(rgbTopLeft)*xcRatio2+Color.blue(rgbTopRight)*xcratio1));
rgbBottomLeft=input.getPixel(xTopLeft,yTopLeft+1);
rgbBottomRight=input.getPixel(xTopLeft+1,yTopLeft+1);
rgbBottomMiddle=Color.argb((int)(Color.alpha(rgbBottomLeft)*xcRatio2+Color.alpha(rgbBottomRight)*xcratio1),//
(int)(Color.red(rgbBottomLeft)*xcRatio2+Color.red(rgbBottomRight)*xcratio1),//
(int)(Color.green(rgbBottomLeft)*xcRatio2+Color.green(rgbBottomRight)*xcratio1),//
(int)(Color.blue(rgbBottomLeft)*xcRatio2+Color.blue(rgbBottomRight)*xcratio1));
}
lastTopLefty=yTopLeft;
if(yt<=yTopLeft+1)
{
// color ratio in favor of the bottom pixel color
ycRatio1=yt-yTopLeft;
ycRatio2=1-ycRatio1;
}
// prepared all pixels to look at, so finally set the new pixel data
output.setPixel(x,y,Color.argb(//
(int)(Color.alpha(rgbTopMiddle)*ycRatio2+Color.alpha(rgbBottomMiddle)*ycRatio1),//
(int)(Color.red(rgbTopMiddle)*ycRatio2+Color.red(rgbBottomMiddle)*ycRatio1),//
(int)(Color.green(rgbTopMiddle)*ycRatio2+Color.green(rgbBottomMiddle)*ycRatio1),//
(int)(Color.blue(rgbTopMiddle)*ycRatio2+Color.blue(rgbBottomRight)*ycRatio1)));
}
}
return output;
}
this algorithm taking nearly 15-20 secs to load the image
is there any possibility to reduce the loading time
if anyone have idea please help me
Thanks in Advance.
Calling getPixel/setPixel methods per pixel may require to much time.
I suggest you to use copyPixelsToBuffer/copyPixelsFromBuffer methods instead.
e.g:
public static Bitmap biLinear(final Bitmap input,int outWidth,int outHeight) {
if(input.getConfig() != Bitmap.Config.ARGB_8888) {
// this example assumes that input bitmap configuration is Bitmap.Config.ARGB_8888
throw new RuntimeException();
}
final int oldHeight=input.getHeight(),oldWidth=input.getWidth();
final int newHeight=outHeight,newWidth=outWidth;
IntBuffer inputBuffer = IntBuffer.allocate(oldWidth * oldHeight);
IntBuffer outputBuffer = IntBuffer.allocate(newWidth * newHeight);
Bitmap output = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
input.copyPixelsToBuffer(inputBuffer);
// position of the top left pixel of the 4 pixels to use interpolation on
int xTopLeft,yTopLeft;
int x,y,lastTopLefty;
final float xRatio=(float)newWidth/(float)oldWidth,yratio=(float)newHeight/(float)oldHeight;
// Y color ratio to use on left and right pixels for interpolation
float ycRatio2=0,ycRatio1=0;
// pixel target in the src
float xt,yt;
// X color ratio to use on left and right pixels for interpolation
float xcRatio2=0,xcratio1=0;
int rgbTopLeft=0,rgbTopRight=0,rgbBottomLeft=0,rgbBottomRight=0,rgbTopMiddle=0,rgbBottomMiddle=0;
// do the resizing:
for(x=0;x<newWidth;x++)
{
xTopLeft=(int)(xt=x/xRatio);
// when meeting the most right edge, move left a little
if(xTopLeft>=oldWidth-1)
xTopLeft--;
if(xt<=xTopLeft+1)
{
// we are between the left and right pixel
xcratio1=xt-xTopLeft;
// color ratio in favor of the right pixel color
xcRatio2=1-xcratio1;
}
for(y=0,lastTopLefty=Integer.MIN_VALUE;y<newHeight;y++)
{
yTopLeft=(int)(yt=y/yratio);
// when meeting the most bottom edge, move up a little
if(yTopLeft>=oldHeight-1)
yTopLeft--;
// we went down only one rectangle
if(lastTopLefty==yTopLeft-1)
{
rgbTopLeft=rgbBottomLeft;
rgbTopRight=rgbBottomRight;
rgbTopMiddle=rgbBottomMiddle;
rgbBottomLeft=inputBuffer.get(xTopLeft + (oldWidth * (yTopLeft+1)));
rgbBottomRight=inputBuffer.get(xTopLeft+1 + (oldWidth * (yTopLeft+1)));
rgbBottomMiddle=Color.argb((int)(Color.alpha(rgbBottomLeft)*xcRatio2+Color.alpha(rgbBottomRight)*xcratio1),//
(int)(Color.red(rgbBottomLeft)*xcRatio2+Color.red(rgbBottomRight)*xcratio1),//
(int)(Color.green(rgbBottomLeft)*xcRatio2+Color.green(rgbBottomRight)*xcratio1),//
(int)(Color.blue(rgbBottomLeft)*xcRatio2+Color.blue(rgbBottomRight)*xcratio1));
}
else if(lastTopLefty!=yTopLeft)
{
// we went to a totally different rectangle (happens in every loop start,and might happen more when making the picture smaller)
rgbTopLeft=inputBuffer.get(xTopLeft + (oldWidth * yTopLeft));
rgbTopRight=inputBuffer.get(xTopLeft+1 + (oldWidth * yTopLeft));
rgbTopMiddle=Color.argb((int)(Color.alpha(rgbTopLeft)*xcRatio2+Color.alpha(rgbTopRight)*xcratio1),//
(int)(Color.red(rgbTopLeft)*xcRatio2+Color.red(rgbTopRight)*xcratio1),//
(int)(Color.green(rgbTopLeft)*xcRatio2+Color.green(rgbTopRight)*xcratio1),//
(int)(Color.blue(rgbTopLeft)*xcRatio2+Color.blue(rgbTopRight)*xcratio1));
rgbBottomLeft=inputBuffer.get(xTopLeft + (oldWidth * (yTopLeft+1)));
rgbBottomRight=inputBuffer.get(xTopLeft+1+(oldWidth * (yTopLeft+1)));
rgbBottomMiddle=Color.argb((int)(Color.alpha(rgbBottomLeft)*xcRatio2+Color.alpha(rgbBottomRight)*xcratio1),//
(int)(Color.red(rgbBottomLeft)*xcRatio2+Color.red(rgbBottomRight)*xcratio1),//
(int)(Color.green(rgbBottomLeft)*xcRatio2+Color.green(rgbBottomRight)*xcratio1),//
(int)(Color.blue(rgbBottomLeft)*xcRatio2+Color.blue(rgbBottomRight)*xcratio1));
}
lastTopLefty=yTopLeft;
if(yt<=yTopLeft+1)
{
// color ratio in favor of the bottom pixel color
ycRatio1=yt-yTopLeft;
ycRatio2=1-ycRatio1;
}
// prepared all pixels to look at, so finally set the new pixel data
outputBuffer.put(x + outWidth * y,Color.argb(//
(int)(Color.alpha(rgbTopMiddle)*ycRatio2+Color.alpha(rgbBottomMiddle)*ycRatio1),//
(int)(Color.red(rgbTopMiddle)*ycRatio2+Color.red(rgbBottomMiddle)*ycRatio1),//
(int)(Color.green(rgbTopMiddle)*ycRatio2+Color.green(rgbBottomMiddle)*ycRatio1),//
(int)(Color.blue(rgbTopMiddle)*ycRatio2+Color.blue(rgbBottomRight)*ycRatio1)));
}
}
output.copyPixelsFromBuffer(outputBuffer);
return output;
}
I am trying to make a simple face detection app consisting of a SurfaceView (essentially a camera preview) and a custom View (for drawing purposes) stacked on top. The two views are essentially the same size, stacked on one another in a RelativeLayout. When a person's face is detected, I want to draw a white rectangle on the custom View around their face.
The Camera.Face.rect object returns the face bound coordinates using the coordinate system explained here and the custom View uses the coordinate system described in the answer to this question. Some sort of conversion is needed before I can use it to draw on the canvas.
Therefore, I wrote an additional method ScaleFacetoView() in my custom view class (below) I redraw the custom view every time a face is detected by overriding the OnFaceDetection() method. The result is the white box appears correctly when a face is in the center. The problem I noticed is that it does not correct track my face when it moves to other parts of the screen.
Namely, if I move my face:
Up - the box goes left
Down - the box goes right
Right - the box goes upwards
Left - the box goes down
I seem to have incorrectly mapped the values when scaling the coordinates. Android docs provide this method of converting using a matrix, but it is rather confusing and I have no idea what it is doing. Can anyone provide some code on the correct way of converting Camera.Face coordinates to View coordinates?
Here's the code for my ScaleFacetoView() method.
public void ScaleFacetoView(Face[] data, int width, int height, TextView a){
//Extract data from the face object and accounts for the 1000 value offset
mLeft = data[0].rect.left + 1000;
mRight = data[0].rect.right + 1000;
mTop = data[0].rect.top + 1000;
mBottom = data[0].rect.bottom + 1000;
//Compute the scale factors
float xScaleFactor = 1;
float yScaleFactor = 1;
if (height > width){
xScaleFactor = (float) width/2000.0f;
yScaleFactor = (float) height/2000.0f;
}
else if (height < width){
xScaleFactor = (float) height/2000.0f;
yScaleFactor = (float) width/2000.0f;
}
//Scale the face parameters
mLeft = mLeft * xScaleFactor; //X-coordinate
mRight = mRight * xScaleFactor; //X-coordinate
mTop = mTop * yScaleFactor; //Y-coordinate
mBottom = mBottom * yScaleFactor; //Y-coordinate
}
As mentioned above, I call the custom view like so:
#Override
public void onFaceDetection(Face[] arg0, Camera arg1) {
if(arg0.length == 1){
//Get aspect ratio of the screen
View parent = (View) mRectangleView.getParent();
int width = parent.getWidth();
int height = parent.getHeight();
//Modify xy values in the view object
mRectangleView.ScaleFacetoView(arg0, width, height);
mRectangleView.setInvalidate();
//Toast.makeText( cc ,"Redrew the face.", Toast.LENGTH_SHORT).show();
mRectangleView.setVisibility(View.VISIBLE);
//rest of code
Using the explanation Kenny gave I manage to do the following.
This example works using the front facing camera.
RectF rectF = new RectF(face.rect);
Matrix matrix = new Matrix();
matrix.setScale(1, 1);
matrix.postScale(view.getWidth() / 2000f, view.getHeight() / 2000f);
matrix.postTranslate(view.getWidth() / 2f, view.getHeight() / 2f);
matrix.mapRect(rectF);
The returned Rectangle by the matrix has all the right coordinates to draw into the canvas.
If you are using the back camera I think is just a matter of changing the scale to:
matrix.setScale(-1, 1);
But I haven't tried that.
The Camera.Face class returns the face bound coordinates using the image frame that the phone would save into its internal storage, rather than using the image displayed in the Camera Preview. In my case, the images were saved in a different manner from the camera, resulting in a incorrect mapping. I had to manually account for the discrepancy by taking the coordinates, rotating it counter clockwise 90 degrees and flipping it on the y-axis prior to scaling it to the canvas used for the custom view.
EDIT:
It would also appear that you can't change the way the face bound coordinates are returned by modifying the camera capture orientation using the Camera.Parameters.setRotation(int) method either.
today I set up the new Android JB 4.3 on my Nexus 7 and i tried to run my application.
Everythings works like it should except one little thing about ImageViews with ScaleType.MATRIX.
Basically what i have in my application is a ImageView as background and accordingly to a ViewPager callbacks i move the focused part of the image updating the Matrix i gave to the imageView using setImageMatix( Matrix matrix ).
the problem seems to be that i can't update the matrix anymore, i just have to instantiate a new one a pass it to the ImageView.
i managed to work around it instantiating everytime a new Matrix but it seems awfully memory expensive compared to the old version.
is this a BUG?
is there a way to udpate the Matrix? ( i by the way already tried to invalidate() the ImageView ecc. )
NOT WORKING
private void updateMatrix( final int page, final double offset ) {
double pagePosition = page + offset;
Matrix matrix = imageView.getImageMatrix();
matrix.setScale( scale, scale );
matrix.postTranslate( - (float) ( pagePosition * pageWidth ) , 0 );
imageView.setImageMatrix( matrix );
imageView.invalidate();
}
WORKING
private void updateMatrix( final int page, final double offset ) {
double pagePosition = page + offset;
Matrix matrix = new Matrix();
matrix.setScale( scale, scale );
matrix.postTranslate( - (float) ( pagePosition * pageWidth ) , 0 );
imageView.setImageMatrix( matrix );
imageView.invalidate();
}
EDIT:
in the first case the image is shown at the top left corner of the ImageView without any scale or translate applied to it, like if the matrix is back to identity.
Just preserve your Matrix as field instead of retrieving it from ImageView and you'll be happy :)
There may be a bug with ImageView scaling starting with 4.3. See my question and answer about this bug.
[UPDATED with additional code]
I'm having a major problem with Android correctly rendering some bitmaps in my custom view's onDraw() method on some (Nexus 7 and 10 that I know about) but not all devices. It renders properly on the Android phones I have for testing. Here is the snippet of consequence:
/* set up mImagePaint earlier */
mImagePaint.setAntiAlias(true);
mImagePaint.setFilterBitmap(true);
mImagePaint.setDither(true);
mImagePaint.setStyle(Paint.Style.FILL_AND_STROKE);
mImagePaint.setStrokeWidth(0);
mImagePaint.setColor(Color.WHITE);
protected void onDraw(Canvas canvas) {
final float vw = getWidth();
final float vh = getHeight();
final float bw = mBitmap.getWidth();
final float bh = mBitmap.getHeight();
final float ba = bw / bh;
final float va = vw / vh;
if (va > ba) {
final float top = (bh - ba / va * bh) / 2;
mSrcRect.set(0, (int) top, (int) bw, (int) (bh - top));
} else {
final float left = (bw - va / ba * bw) / 2;
mSrcRect.set((int) left, 0, (int) (bw - left), (int) bh);
}
mContentRect.set(0, 0, vw, vh);
canvas.drawBitmap(mBitmap, mSrcRect, mContentRect, mImagePaint);
}
Results on Nexus 7 and 10 are incorrect and renders a wide white border as shown below. This is part of the bitmap rendering but not part of the original bitmap or rect.
The correct (desired) result on a Samsung Galaxy phone:
The image and code shown in both examples above are exactly the same. I've tried variations already using null paint, null srcRect, and even using alternate method drawBitmap(bitmap, 0, 0, null) and get the same results. Looking at the framework code, of course drawBitmap() calls directly to native methods whose source code I can't view.
Only a small number of images seem to exhibit this problem and seems as though mostly square images exhibit it. But here is only one other non-square image that exhibits this problem:
Most of these images are slightly rotated given the desired custom view, and it now occurs to me that rotation might be part of the problem, but maybe not since the canvas isn't rotated, just when it's copied to the parent's canvas bitmap backing by Android.
Any ideas? This is nuts!
Are you sure the pictures are large enough to display without you having to scale up on those devices?