Canvas scaling retains pixel size - android

I'm trying to do some image processing on the camera preview, but being too slow I under sample time image and do the processing on a smaller scale. Then I overlay the processed (thresholded) image over the preview. My problem is that when I scale the smaller image to fit the camera preview the pixels stay the same size... I would like to see some big pixels due to the scaling, but I can't.
Here's the XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
android:orientation="vertical" >
<SurfaceView
android:id="#+id/svPreview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<SurfaceView
android:id="#+id/svOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true" />
</RelativeLayout>
And the thread that gets the camera preview image bytes and does the thresholding:
class PathThread extends Thread implements PreviewCallback
{
private final int width, height;
private final SurfaceHolder holder;
private Canvas overlayCanvas;
private int samples = 8;
final private float scale;
final private Paint red, blue, yellow;
public PathThread(int canvasWidth, int canvasHeight, SurfaceView canvasView)
{
// This is how much smaller image I want
width = canvasWidth / samples;
height = canvasHeight / samples;
holder = canvasView.getHolder();
// Scale to fit the camera preview SurfaceView
scale = (float) canvasView.getWidth() / height;
// No smoothing when scaling please
yellow = new Paint();
yellow.setColor(Color.parseColor("#ffbb33"));
yellow.setAntiAlias(false);
yellow.setDither(false);
yellow.setFilterBitmap(false);
}
public void onPreviewFrame(byte[] data, Camera camera)
{
overlayCanvas = holder.lockCanvas();
overlayCanvas.drawColor(0, Mode.CLEAR);
overlayCanvas.scale(scale, scale);
// Do the image processing and make a [samples] times smaller image
// Boring, pseudo-optimized, per-pixel thresholding process
holder.unlockCanvasAndPost(overlayCanvas);
}
}
This is the result:
I'm aware that it's a little bit offset, but that's the least of my problems.

Solution:
After 2 more days of searching it was quite simple, but not very easy to find/understand:
With the holder of the SurfaceView where I draw I had to call this:
canvasHolder.setFixedSize(width, height);
I experimented with different values and found what I needed. This makes big pixels.

Related

Scale factor in painting app is wrong

My goal is to move my finger over the screen to paint on a picture in an imageview mImageView. The picture there is resized to imageview-size to improve performance. Later I want to export this to the big image again, this is where scaleFactor comes in. When multiplying the real device coordinates with this factor, I should get where paint on the real images goes.
path is the uri to the underlying picture.
But it's always off, the areas I want to paint are not being painted in the final image. In general, scaleFactor is too big. Where is the problem? Or is this code right and the problem likely elsewhere?
scaleFactor is defined here for the first time.
private void displayFunction(Uri path) {
iv = (ImageView)findViewById(R.id.mImageView);
int ivWidth = iv.getWidth();
int ivHeight = iv.getHeight();
Bitmap v = null;
try {
v = MediaStore.Images.Media.getBitmap(this.getContentResolver(), path);
}
catch (Exception vc)
{
return;
}
if (v == null) return;
int originalWidth = v.getWidth();
int originalHeight = v.getHeight();
float rs = (float)originalWidth / (float)originalHeight;
Bitmap bitmap2 = null;
if (rs > 1)
bitmap2 = getResizedBitmap(v,(int)((float)ivWidth / rs),ivWidth);
else
bitmap2 = getResizedBitmap(v,ivHeight,(int)((float)ivHeight * rs));
scaleFactor = Math.max((float)originalWidth/(float)ivWidth, (float)originalHeight/(float)ivHeight);
v = null;
bmpTemp = clearPainting(bitmap2, Color.RED, 1, Paint.Style.STROKE);
iv.post(new Runnable() {
public void run() {
iv.setScaleType(ImageView.ScaleType.FIT_XY);
iv.setImageBitmap(bmpTemp);
}
});
}
and
<android.support.design.widget.CoordinatorLayout
android:id="#+id/myCoordinatorLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ads="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="de.noahsofie.bildzuschneiden.MainActivity">
<ImageView
android:id="#+id/mImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:contentDescription="#string/html"
android:scaleType="centerCrop" />
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
You want to display the big image only or paint on it as well? If the goal is to display the large bitmap only, you can draw the paths on the bitmap using canvas, and then scale the bitmap to any size that you want.

Activity runs slow with a couple of ImageView-s

I have an activity containing 4 images in total. They are all matching the resolution of a 1080x1920 device. When I run the activity with those images, which are loaded directly in my activity via the XML, it runs tremendously slow in my Genymotion emulator and lags on a real Android device.
Here is the setup:
<android.support.design.widget.AppBarLayout
...>
<android.support.design.widget.CollapsingToolbarLayout
...>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="vertical"
app:layout_collapseMode="parallax">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/imageView"
android:src="#drawable/shot_header"
android:scaleType="centerCrop" />
</LinearLayout>
<android.support.v7.widget.Toolbar
.../>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
The first image is in a CollapsingToolbarlayout. The resolution of the image is 1080x649 PNG.
The content_activity:
This image fills the parent width.It's resolution is 1080x772 PNG.
<ImageView
android:layout_width="match_parent"
android:layout_height="250dp"
android:id="#+id/main_image"
android:layout_below="#+id/shot_error_field"
android:src="#drawable/forehand_midpng"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_marginTop="15dp"/>
The other 2 images are in a LinearLayout, their resolution is 500x399
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/main_image">
<ImageView
android:layout_width="150dp"
android:layout_height="150dp"
android:id="#+id/imageView3"
android:src="#drawable/forehand_mid_wrong"
android:layout_weight="1"/>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" >
</View>
<ImageView
android:layout_width="150dp"
android:layout_height="150dp"
android:id="#+id/imageView4"
android:src="#drawable/forehand_mid_wrong"
android:layout_weight="1"/>
</LinearLayout>
To summarize, I have an activity with 4 ImageViews, populated with properly sized images, which should no problem for a modern Android device. The problem is that this activity is running extremely slow and lagging due to a high memory consumption.
Am I doing something wrong? How can I further optimize those images?
I looked into other threads- out of memory issue but none seems to propose a solution to such a problem.
Problem is resolution of the image, if you can reduce resolution of the image then work fine, here is some example for reducing image resolution and size.
If you pass bitmap width and height then use below function.
public Bitmap getResizedBitmap(Bitmap image, int bitmapWidth,
int bitmapHeight) {
return Bitmap.createScaledBitmap(image, bitmapWidth, bitmapHeight,
true);
}
if you want bitmap ratio same and reduce bitmap size. then pass your maximum size bitmap. you can use this function
public Bitmap getResizedBitmap(Bitmap image, int maxSize) {
int width = image.getWidth();
int height = image.getHeight();
float bitmapRatio = (float)width / (float) height;
if (bitmapRatio > 0) {
width = maxSize;
height = (int) (width / bitmapRatio);
} else {
height = maxSize;
width = (int) (height * bitmapRatio);
}
return Bitmap.createScaledBitmap(image, width, height, true);
}
or if you are using drawable resources then use this method
public Drawable resizeImage(int imageResource) {// R.drawable.large_image
// Get device dimensions
Display display = getWindowManager().getDefaultDisplay();
double deviceWidth = display.getWidth();
BitmapDrawable bd = (BitmapDrawable) this.getResources().getDrawable(
imageResource);
double imageHeight = bd.getBitmap().getHeight();
double imageWidth = bd.getBitmap().getWidth();
double ratio = deviceWidth / imageWidth;
int newImageHeight = (int) (imageHeight * ratio);
Bitmap bMap = BitmapFactory.decodeResource(getResources(), imageResource);
Drawable drawable = new BitmapDrawable(this.getResources(),
getResizedBitmap(bMap, newImageHeight, (int) deviceWidth));
return drawable;
}
/************************ Resize Bitmap *********************************/
public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height,
matrix, false);
return resizedBitmap;
}
Actually this should not be an issue and these images are not so big to make you phone laggy. Take a look on other stuff you have in the application, it is possible there is some heavy operations (like DB writing/readin, API requests) right in UI thread.
If there is no such operations and you see such problems with the perfomance, try to set these images via some libraries, like Picasso.
Gradle dependency:
compile 'com.squareup.picasso:picasso:2.4.0'
And code will look like this:
Picasso.with(this).load(R.drawable.my_image).into(toolbar);
TRY
Definitely reduce the size of the images.
Cache images if you can.
Download the images on a different thread. Store a HashMap would
make it easy for you
When you get the list of image urls, iterate through them:
pictures.put(id,view);
try{
FileInputStream in = openFileInput(id);
Bitmap bitmap = null;
bitmap = BitmapFactory.decodeStream(in, null, null);
view.setImageBitmap(bitmap);
}catch(Exception e){
new Thread(new PictureGetter(this,mHandler,id)).start();
}
Code to update the image view:
if(id!=null){
ImageView iv = pictures.get(id);
if(iv!=null){
try{
FileInputStream in = openFileInput(id);
Bitmap bitmap = null;
bitmap = BitmapFactory.decodeStream(in, null, null);
iv.setImageBitmap(bitmap);
}catch(Exception e){
}
}
Try facebook's fresco library. It can handle large images and one of my projects, reduces memory consumption about 50%.

Scale and crop image in XML

I'm trying to scale and crop image at the same time and show it from left to right screen edge. I receive image that is just little bit wider than users screen and I'm able to scale it like this (XML):
<ImageView
android:id="#+id/category_image_top"
android:layout_width="match_parent"
android:layout_height="170dp"
android:maxHeight="170dp"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:focusable="false"
/>
But this is what I get:
I would like to align image to top right like so:
Is this possible? I've tried all scaleTypes but noting works, image is either scaled to fit by X and Y (fitXY, fitStart) or image cropped but centered (centerCrop). I need something like android:scaleType="cropStart"
<ImageView
android:id="#+id/category_image_top"
android:layout_width="match_parent"
android:layout_height="170dp"
android:maxHeight="170dp"
android:scaleType="centerCrop"
android:paddingLeft="half of your screen width"
android:paddingBottom="half of your screen height"
android:adjustViewBounds="true"
android:focusable="false"
/>
You can set padding to move image left or right and also top and bottom padding to move up and down
As I didn't find a way to deal with this situation through xml (views) I turned (as #serenskye suggested) to code. Here's my code, I hope it helps (ps: I've changed my logic a little bit, I wanted to fit image by width so I've scaled it to predefined imageWidght and then cropped it to imageHeight)
//bm is received image (type = Bitmap)
Bitmap scaledImage = null;
float scaleFactor = (float) bm.getWidth() / (float) imageWidth;
//if scale factor is 1 then there is no need to scale (it will stay the same)
if (scaleFactor != 1) {
//calculate new height (with ration preserved) and scale image
int scaleHeight = (int) (bm.getHeight() / scaleFactor);
scaledImage = Bitmap.createScaledBitmap(bm, imageWidth, scaleHeight, false);
}
else {
scaledImage = bm;
}
Bitmap cropedImage = null;
//if cropped height is bigger then image height then there is no need to crop
if (scaledImage.getHeight() > imageHeight)
cropedImage = Bitmap.createBitmap(scaledImage, 0, 0, imageWidth, imageHeight);
else
cropedImage = scaledImage;
iv.setImageBitmap(cropedImage);
add
android:layout_gravity="center"

Android:How to scale canvas to fit in desired size

I have created this class which extends View and draws a graph in Canvas. While drawing a graph I am considering actual screen height and width and it draws a graph according to that.
My problem is I am using this View as a widget in another activity with a desired size say 100 by 150..but the View class doesn't scale itself and only shows a clipped part of it not the graph. I know O have to do some calculation to fit this canvas into desired size but I have no clue how to do that..Any help would be great!!
Code where I am drawing graph is
public class GraphPlotting extends View
{
public void onDraw(Canvas canvas)
{
readPoints();
Paint paint1 = new Paint();
paint1.setColor(Color.rgb(0xFF,0xFF, 0xFF));
paint1.setStrokeWidth(1.0f);
Paint paint2 = new Paint();
paint2.setColor(Color.rgb(0xFF,0xFF, 0xFF));
paint2.setStrokeWidth(10.0f);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
canvas.drawPaint(paint);
canvas.drawLines(p,paint1);
}
public void readPoints()
{
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
height = dm.heightPixels;
width = dm.widthPixels;
// drawing graph based on above dimension which in my case is 480 by 800
}
xml Layout where I am using this View
<RelativeLayout
android:layout_width="200dp"
android:layout_height="200dp"
android:orientation="vertical"
android:layout_below="#+id/btn_analyze"
android:layout_centerHorizontal="true"
android:id="#+id/rlgraph"
>
<com.aventusoft.mylynel.GraphPlotting
android:id="#+id/view1"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
I want to fit my canvas with my Relative layout height and width and graph should visible in that area..currently its showing the View but only part of it..Graph is not visible at all..Please help me in calculation I need to do..
Copy/paste direct from one of my own classes which extends ImageView to draw a graph. The idea is that you can put this into any layout. The class takes care of it's own measurements.
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
widgetHeight = h;
widgetWidth = w;
if (sizeChangedObservers != null && sizeChangedObservers.size() > 0) {
for (SizeChangedObserver observer : sizeChangedObservers) {
observer.callback(w, h);
}
}
updateIncrements();
}
The sizeChangedObservers just let's me notify other classes if the graph size changes, e.g. I have scale indicators on the X and Y axis. updateIncrements() is where I figure out how many pixels each X and Y represents using the device screen resolution, graph size and DPI count.
Using this approach, the canvas will be whatever size it is and you don't care. When you plot, you just use x * xPixels and y * yPixels which are the calculated pixels per value.
[EDIT] Try reading this first http://developer.android.com/training/custom-views/custom-drawing.html

Android: high quality image resizing / scaling

I need to scale down images coming from a Network stream without losing quality.
I am aware of this solution Strange out of memory issue while loading an image to a Bitmap object but it is too coarse - inSampleSize is an integer and does not allow finer control over the resulting dimensions. That is, I need to scale images to specific h/w dimensions (and keeping aspect ratio).
I dont mind having a DIY bicubic/lancoz algorithm in my code but I cant find any examples that would work on Android as they all rely on Java2D (JavaSE).
EDIT:
Ive attached a quick source. The original is 720x402 HD screen capture. Please ignore the top 2 thumbnails. The top large image is resized automatically by android (as part of layout) to about 130x72. It is nice and crisp. The bottom image is resized with API and has severe artifacting
I've also tried using the BitmapFactory and, as I said earlier, it has two problems - no way to scale to exact size and the scaled image is blurry.
Any ideas on how to fix the artifcating?
Thanks, S.O.!
package qp.test;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.Bundle;
import android.widget.ImageView;
public class imgview extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Bitmap original = BitmapFactory.decodeResource(getResources(), R.drawable.a000001570402);
Bitmap resized = getResizedBitmap(original, 130);
//Bitmap resized = getResizedBitmap2(original, 0.3f);
System.err.println(resized.getWidth() + "x" + resized.getHeight());
ImageView image = (ImageView) findViewById(R.id.ImageViewFullManual);
image.setImageBitmap(resized);
}
private Bitmap getResizedBitmap(Bitmap bm, int newWidth) {
int width = bm.getWidth();
int height = bm.getHeight();
float aspect = (float)width / height;
float scaleWidth = newWidth;
float scaleHeight = scaleWidth / aspect; // yeah!
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth / width, scaleHeight / height);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
bm.recycle();
return resizedBitmap;
}
private Bitmap getResizedBitmap2(Bitmap bm, float scale) {
/* float aspect = bm.getWidth() / bm.getHeight();
int scaleWidth = (int) (bm.getWidth() * scale);
int scaleHeight = (int) (bm.getHeight() * scale);
*/
// original image is 720x402 and SampleSize=4 produces 180x102, which is
// still too large
BitmapFactory.Options bfo = new BitmapFactory.Options();
bfo.inSampleSize = 4;
return BitmapFactory.decodeResource(getResources(), R.drawable.a000001570402, bfo);
}
}
And the layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<!-- <TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="hullo" android:background="#00ff00"
/>
-->
<ImageView android:id="#+id/ImageViewThumbAuto"
android:layout_width="130dip" android:layout_height="72dip"
android:src="#drawable/a000001570402" />
<ImageView android:id="#+id/ImageViewThumbManual"
android:layout_width="130dip" android:layout_height="72dip"
android:src="#drawable/a000001570402"
android:layout_toRightOf="#id/ImageViewThumbAuto"
/>
<ImageView android:id="#+id/ImageViewFullAuto" android:layout_width="300dip"
android:layout_height="169dip"
android:scaleType="fitXY"
android:src="#drawable/a000001570402"
android:layout_below="#id/ImageViewThumbAuto"
/>
<ImageView android:id="#+id/ImageViewFullManual" android:layout_width="300dip"
android:layout_height="169dip"
android:scaleType="fitXY"
android:src="#drawable/a000001570402"
android:layout_below="#id/ImageViewFullAuto"
/>
</RelativeLayout>
You can use BitmapFactory.Options with BitmapFactory.decode function(s),
using inDensity and inTargetDensity
Example: you have 1600x1200 size of image and want to resize to 640x480
then 'inDensity'=5 and 'inTargetDensity'=2 (1600x2 equal to 640x5).
Hoping this help.
The top large image is simply scaled down to 450px by the layout so no artifacts.
The artifacts of the bottom large image result from scaling it down to 130px wide and then up again to about 450px by the layout. So the artifacts are made by your scaling. Try
Bitmap resized = getResizedBitmap(original, 450);
in your code and it should be fine. However, you need to adapt that to the actuall screen width of the phone either.
Briefly, good downscaling algorithm (not nearest neighbor like) consists of 2 steps:
downscale using BitmapFactory.Options::inSampleSize->BitmapFactory.decodeResource() as close as possible to the resolution that you need but not less than it
get to the exact resolution by downscaling a little bit using Canvas::drawBitmap()
Here is detailed explanation how SonyMobile resolved this task: http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/
Here is the source code of SonyMobile scale utils: http://developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-android/

Categories

Resources