for my app I am creating an image editor which currently works fine but I want to implement un undo button which basically goes back to the previous state of the image. I am trying to do this by using an ArrayList of bitmaps. At first I use this code to open an image from gallery and set canvas:
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inJustDecodeBounds = true;
bmp = BitmapFactory.decodeStream(getContentResolver().openInputStream(
imageFileUri), null, bmpFactoryOptions);
bmpFactoryOptions.inJustDecodeBounds = false;
bmp = BitmapFactory.decodeStream(getContentResolver().openInputStream(
imageFileUri), null, bmpFactoryOptions);
alteredBitmap = Bitmap.createBitmap(bmp.getWidth(), bmp
.getHeight(), bmp.getConfig());
int x = alteredBitmap.getWidth();
int y = alteredBitmap.getHeight();
if (x > y) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
lscape = true;
} else {
lscape = false;
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
width = size.x;
height = size.y;
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
paint.setStyle(Paint.Style.FILL);
String[] orientationColumn = {MediaStore.Images.Media.ORIENTATION};
Cursor cur = managedQuery(imageFileUri, orientationColumn, null, null, null);
int orientation = -1;
if (cur != null && cur.moveToFirst()) {
orientation = cur.getInt(cur.getColumnIndex(orientationColumn[0]));
}
Matrix matrix = new Matrix();
if (orientation!=90) {
matrix.postRotate(orientation);
scaledBitmap = Bitmap.createScaledBitmap(alteredBitmap, width, height, false);
canvas = new Canvas(scaledBitmap);
canvas.drawBitmap(bmp, matrix, paint);
choosenImage.setImageBitmap(scaledBitmap);
choosenImage.setOnTouchListener(this);
b.add(scaledBitmap);
}
At some point of code I use this to draw a straight line:
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downx = event.getX();
downy = event.getY();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
upx = event.getX();
upy = event.getY();
canvas.drawLine(getScaledWidth(downx, width, choosenImage.getWidth()), downy / choosenImage.getHeight() * height, getScaledWidth(upx, width, choosenImage.getWidth()), upy / choosenImage.getHeight() * height, paint);
choosenImage.invalidate();
b.add(scaledBitmap);
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
Now what should I write for my onclick on undo button? I've tried everything but dont know how to reset the canvas to display one of the bitmaps saved in the arraylist...dont know if its a problem with my way of setting the canvas to the bitmaps or if im missing something. This was my latest attempt:
scaledBitmap = b.get(b.size()-2);
b.remove(b.size() - 1);
choosenImage.invalidate();
Im new to android development so sorry if I'm not being clear, but really any help would be greatly appreciated I am stuck in this project and it is due in soon.
Thanks in advance!
List of bitmaps --> Out of memory!
Bitmaps are kept in a very limited area of memory and having a whole list of fullscreen bitmaps for und purposes will make your app go out of memory very quickly.
List of paths --> Better
What I did for my (very old) app "Fingercolors", is keeping a list of all paths the user paints. The paths consist of a list of points and some information about color, stroke-width etc.
For undoing a step, I start (offscreen) with an empty bitmap and repaint all paths from the list except for the last one and then show that rebuilt bitmap.
This allows unlimited undo and also redo and it is fast enough even for complex images with lots of paths.
Related
My app uses geojson to draw features on map. To draw feature:
for (GeoJsonFeature f : new GeoJsonLayer(googleMap, new JSONObject(geojson)).getFeatures())
{
f.setPointStyle(getPointStyle(text,status);
layer.addFeature(f);
}
layer.addLayerToMap();
To generate point (marker) style and set icon:
GeoJsonPointStyle markerStyle = new GeoJsonPointStyle();
int[] markers = {R.drawable.marker0,R.drawable.marker1,R.drawable.marker2,R.drawable.marker3,R.drawable.marker4};
bmp = Utils.drawTextToBitmap(getContext(), markers[status], icon);
markerStyle.setIcon(highlighted ? BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN):BitmapDescriptorFactory.fromBitmap(bmp));
drawTextToBitmap just generates bitmap with text on it:
public static Bitmap drawTextToBitmap(Context gContext, int gResId, String gText)
{
Resources resources = gContext.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = BitmapFactory.decodeResource(resources, gResId);
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
if (bitmapConfig == null)
{
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.WHITE);
paint.setTextSize((int) (18 * scale));
Typeface tf = Typeface.createFromAsset(gContext.getAssets(), "fonts/fontawesome.ttf");
paint.setTypeface(Typeface.create(tf, Typeface.BOLD));
paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);
Rect bounds = new Rect();
paint.getTextBounds(gText, 0, gText.length(), bounds);
int x = (bitmap.getWidth() - bounds.width()) / 2;
int y = (bitmap.getHeight() + bounds.height()) / 2 -8;
canvas.drawText(gText, x, y, paint);
return bitmap;
}
My problem is that on some phones (not on every phone) this approach causes huge memory leak if there is big amount of markes to draw on map. Problem does not exists if I use static marker drawable from resources, but I need dynamic bitmap to add text on it.
Do you have any idea about memory leak cause? It seems map holds bitmap reference so it prevents garbage from disposing it. Even if map fragment is destroyed, map resources will be never released, so after displaying map few times, app stops working due to OutOfMemory exception.
I have a Bitmap drawn in a rectangle. I want to rotate the Bitmap to 90 degree and draw the oriented Bitmap in the same rectangle bound, for that I may need to scale it.
Above the oriented image is not scaled to fit the rectangle bound of original rectangle (see the first image, the normal one) and also it does remove the lower part of the image. Below is code snippet. Any suggestion ?
sourceImageRect.left = 0;
sourceImageRect.top = 0;
sourceImageRect.right = bmp.getWidth();
sourceImageRect.bottom = bmp.getHeight();
destImageRect.top = destTop;
destImageRect.left = destLeft;
destImageRect.right = destLeft + destWidth;
destImageRect.bottom = destTop + destHeight;
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate(90, (destLeft + (destWidth / 2)),
(destTop + (destHeight / 2)));
canvas.drawBitmap(bmp, sourceImageRect, destImageRect, null);
canvas.restore();
Last image is exact required output I need.
I writed a tool java class named ImageScaleUtil.java several days ago. Helping you can get some useful information on it.
You can use it, like this:
```java
Bitmap targetBitmap = ImageScaleUtil.getScaleBitmap(Bitmap sourceBitmap, int reqWidth, int reqHeight);
```
As of #pskink pastebin reference, following way I resolved and it's working code,
Matrix matrix = new Matrix();
matrix.postRotate(90);
Bitmap bmpRotated = Bitmap.createBitmap(bmpSource, 0, 0, bmpSource.getWidth(), bmpSource.getHeight(), matrix, true);
sourceImageRect.left = 0;
sourceImageRect.top = 0;
sourceImageRect.right = bmpRotated.getWidth();
sourceImageRect.bottom = bmpRotated.getHeight();
destImageRect.top = destTop;
destImageRect.left = destLeft;
destImageRect.right = destLeft + destWidth;
destImageRect.bottom = destTop + destHeight;
canvas.drawBitmap(bmpRotated, sourceImageRect, destImageRect, null);
However, I managed to create Bitmap outside of OnDraw()
I'm trying to use a camera in my app and I want to be able to use it in landscape and portrait mode. I'm having no difficulty creating pictures in landscape mode, but I haven't found a good way to save pictures in portrait mode.
When I want to make a picture in portrait mode I need to set the displayorientation to portrait first, like this:
switch (windowManager.getDefaultDisplay().getRotation()) {
case android.view.Surface.ROTATION_0:
mCamera.setDisplayOrientation(90);
break;
case android.view.Surface.ROTATION_90:
mCamera.setDisplayOrientation(0);
break;
case android.view.Surface.ROTATION_180:
mCamera.setDisplayOrientation(270);
break;
case android.view.Surface.ROTATION_270:
mCamera.setDisplayOrientation(180);
break;
}
But then the picture is still saved in landscape mode. One solution I found was to change the rotation parameter of the camera like this:
public void onOrientationChanged(int orientation) {
if (orientation == ORIENTATION_UNKNOWN) return;
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
orientation = (orientation + 45) / 90 * 90;
int rotation = 0;
if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
rotation = (info.orientation - orientation + 360) % 360;
} else { // back-facing camera
rotation = (info.orientation + orientation) % 360;
}
mParameters.setRotation(rotation);
}
The problem with this approach is that it may just set the orientation in the EXIF header and not actually rotate the picture (which is the case with the device I'm using).
Another approach was rotating the actual data after the picture is taken like this:
Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
int w = bitmap.getWidth();
int h = bitmap.getHeight();
Matrix mtx = new Matrix();
mtx.postRotate(rotate);
bitmap= Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
currentData = stream.toByteArray();
But this approach takes 10 seconds (which is too long) and while I could put this code in an AsyncTask, I need the data one to a few seconds later, so then I'd still need to wait.
So far I haven't found a better solution.
I have found a solution that makes me still need to wait for a second or two, but that's fast enough for me.
It's a pretty simple fix, just use the Matrix.postRotate method, but change:
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
to:
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
For me this is a pretty good solution, especially because everywhere else I already used .jpg images so it didn't make much sense compressing the Bitmap to a .png anyway.
I am making an OCR app for Android, that will take a screenshot of some text, recognise it and search a key word on Google. If you haven't already realized, I'm trying to make a "Google Now on Tap" clone.
To make the OCR work better, I am first rotating the image, then filtering the image. First by getting rid of the status bar and the navigation bar, then converting it to grayscale, then sharpening.
But the image quality after filtering the image is extremely pixelated, and this greatly effects OCR accuracy.
Here are the images, before and after (just of an IFTTT email I got)
As you can see, the before image is much higher quality than the filtered and rotated one.
Here is my code for rotating, filtering and saving the image:
Firstly taking screenshot, then saving the screenshot.
public void getScreenshot()
{
try
{
Process sh = Runtime.getRuntime().exec("su", null, null);
OutputStream os = sh.getOutputStream();
os.write(("/system/bin/screencap -p " + _path).getBytes("ASCII"));
os.flush();
os.close();
sh.waitFor();
onPhotoTaken();
Toast.makeText(this, "Screenshot taken", Toast.LENGTH_SHORT).show();
}
catch (IOException e)
{
System.out.println("IOException");
}
catch (InterruptedException e)
{
System.out.println("InterruptedException");
}
}
Then, rotate the image:
protected void onPhotoTaken() {
_taken = true;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeFile(_path, options);
try {
ExifInterface exif = new ExifInterface(_path);
int exifOrientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
Log.v(TAG, "Orient: " + exifOrientation);
int rotate = 0;
switch (exifOrientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
rotate = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotate = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = 270;
break;
}
Log.v(TAG, "Rotation: " + rotate);
if (rotate != 0) {
// Getting width & height of the given image.
int w = bitmap.getWidth();
int h = bitmap.getHeight();
// Setting pre rotate
Matrix mtx = new Matrix();
mtx.preRotate(rotate);
// Rotating Bitmap
bitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, false);
}
// Convert to ARGB_8888, required by tess
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
} catch (IOException e) {
Log.e(TAG, "Couldn't correct orientation: " + e.toString());
}
// _image.setImageBitmap( bitmap );
setImageFilters(bitmap);
}
Then, filter the image:
public void setImageFilters(Bitmap bmpOriginal)
{
//Start by cropping image
Bitmap croppedBitmap = ThumbnailUtils.extractThumbnail(bmpOriginal, 1080, 1420);
//Then convert to grayscale
int width, height;
height = 1420;
width = 1080;
Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bmpGrayscale);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(croppedBitmap, 0, 0, paint);
//Finally, sharpen the image
double weight = 11;
double[][] sharpConfig = new double[][]
{
{ 0 , -2 , 0 },
{ -2, weight, -2 },
{ 0 , -2 , 0 }
};
ConvolutionMatrix convMatrix = new ConvolutionMatrix(3);
convMatrix.applyConfig(sharpConfig);
convMatrix.Factor = weight - 8;
Bitmap filteredBitmap = ConvolutionMatrix.computeConvolution3x3(bmpGrayscale, convMatrix);
//Start Optical Character Recognition
startOCR(filteredBitmap);
//Save filtered image
saveFiltered(filteredBitmap);
}
Then, saving the filtered and rotated image:
public void saveFiltered(Bitmap filteredBmp) {
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
filteredBmp.compress(Bitmap.CompressFormat.JPEG, 20, bytes);
//You can create a new file name "test.jpg" in sdcard folder.
File f = new File("/sdcard/SimpleAndroidOCR/ocrgray.jpg");
f.createNewFile();
//Write the bytes in file
FileOutputStream fo = new FileOutputStream(f);
fo.write(bytes.toByteArray());
//Remember close the FileOutput
fo.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Thanks heaps for anyone taking the time to help.
It was actually in my onPhotoTaken method. After taking and saving the screenshot in get screenshot, I am reading the file from the location it was saved to, then filtering it. I changed this line in the onPhotoTaken method:
options.inSampleSize = 4 to options.inSampleSize = 1
It does look like the jpeg compression is messing the image up. Try using a format better suited for images with sharp edges, such as of text. I would recommend png or even gif. You could also store the uncompressed BMP.
Jpeg compression works by exploiting the fact that in most pictures (nature, people, objects), sharp edges are not that visible to the human eye. This makes it really bad for storing sharp edged content, such as text.
Also, your image filter is effectively removing the anti-aliasing of the image, which further decreases the perceived image quality. That might be what you want to do, however, since it might make OCR easier.
I also missed the sampling size due to the images you uploaded being the same size here on the site. From the Android documentation:
If set to a value > 1, requests the decoder to subsample the original
image, returning a smaller image to save memory. The sample size is
the number of pixels in either dimension that correspond to a single
pixel in the decoded bitmap. For example, inSampleSize == 4 returns an
image that is 1/4 the width/height of the original, and 1/16 the
number of pixels. Any value <= 1 is treated the same as 1. Note: the
decoder uses a final value based on powers of 2, any other value will
be rounded down to the nearest power of 2.
Setting options.inSampleSize = 4; to 1 instead will increase the quality.
I have a requirement where I will be having a background Image and one mask Image. Lets say the background Image is of Motorcycle and the mask will be used to hide the registration number. So the mask will be movable and also resize-able. Finally the combined Image will be generated with background image and Image mask. How we can achieve this ?
public Bitmap drawFacesOnBitmap(Context gContext, Bitmap bitmap) {
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
// set default bitmap config if none
if(bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
// resource bitmaps are imutable,
// so we need to convert it to mutable one
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);
// new antialised Paint
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
// text color - #3D3D3D
paint.setColor(Color.GREEN);
// draw text to the Canvas center
Log.d("BITMAP DIMENS****", "bitmapWidth:"+bitmap.getWidth()+" & bitmapHeight:"+bitmap.getHeight());
android.graphics.Rect r = new android.graphics.Rect();
r.left = 200;
r.top = 200;
r.right = 400;
r.bottom = 400;
canvas.drawRect(r, paint);
return bitmap;
}
You can use this code to draw a rectangle on a bitmap. Edit it to meet your needs.