I am coding an app where I take a picture and I pass the picture into an image recognition model. The problem is, the recognition only takes 150528 bytes, and the number of bytes in the image depends on the image quality. Is there a way in Java to crop the top and bottom of the image as evenly as possible depending on the quality to make it a set number of bytes each time?
This is my code for this part so far.
int width = 100;
int height = 200;
final ImageReader reader = ImageReader.newInstance(width,height,ImageFormat.JPEG,1);
List<Surface> outputSurface = new ArrayList<>(2);
outputSurface.add(reader.getSurface());
outputSurface.add(new Surface(textureView.getSurfaceTexture()));
As you can see, I'm restricting the size of the image captured, but the number of bytes depends on the quality so I need to actually just crop the image. How can I do this?
You can crop/ resize image with the following
byte[] thumb_byte_data;
Uri resultUri = ImageUri;
//getting imageUri and store in file. and compress to bitmap
File file_path = new File(resultUri.getPath());
try {
Bitmap thumb_bitmap = new Compressor(this)
.setMaxHeight(200)
.setMaxWidth(200)
.setQuality(75)
.compressToBitmap(file_path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
thumb_bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
thumb_byte_data = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
Related
This question already has answers here:
Decrease image size without losing its quality in android
(6 answers)
Closed 4 years ago.
When the user selects an image from the gallery, I convert it into a base64 string. Then I use an API. The image size must be less than 2MB, so if the image from the gallery is bigger than 2MB, I need to reduce its size.
How should I do that?
That's my code:
// I pass the URI of the image to the method
#Override
protected Integer doInBackground(Uri... uris) {
InputStream inputStream = null;
try {
inputStream = getContentResolver().openInputStream(uris[0]);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
byte[] buffer = new byte[8192];
int bytesRead;
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
while ((bytesRead = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
bytes = output.toByteArray();
String encodedImage = Base64.encodeToString(bytes, Base64.NO_WRAP);
You can create a Bitmap object using
BitmapFactory.decodeByteArray
Decode an immutable bitmap from the specified byte array.
and then you can use
bitmap.compress
Write a compressed version of the bitmap to the specified
outputstream. If this returns true, the bitmap can be reconstructed by
passing a corresponding inputstream to BitmapFactory.decodeStream().
Note: not all Formats support all bitmap configs directly, so it is
possible that the returned bitmap from BitmapFactory could be in a
different bitdepth, and/or may have lost per-pixel alpha (e.g. JPEG
only supports opaque pixels).
This method may take several seconds to complete, so it should only be
called from a worker thread.
and/or
Bitmap.createScaledBitmap
Creates a new bitmap, scaled from an existing bitmap, when possible.
If the specified width and height are the same as the current width
and height of the source bitmap, the source bitmap is returned and no
new bitmap is created.
In my project I created two utility functions that might help you understand how to use the functions I mentioned above to fit your needs
public static Bitmap base64ToBitmap(String encodedImage) {
byte[] decodedString = Base64.decode(encodedImage, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
}
public static String bitmapToBase64(Bitmap bitmap, int quality, int percentScale) {
if (quality < 0 || quality > 100) {
quality = IMAGE_QUALITY_DEFAULT_VALUE;
}
if (percentScale < 0 || percentScale > 100) {
percentScale = IMAGE_SCALE_DEFAULT_VALUE;
}
float scale = (float) percentScale / 100;
int width = Math.round(bitmap.getWidth() * scale);
int height = Math.round(bitmap.getHeight() * scale);
bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
byte[] byteArray = byteArrayOutputStream.toByteArray();
return Base64.encodeToString(byteArray, Base64.NO_WRAP);
}
One way to easily check the image and store it is according to the developer docs
https://developer.android.com/topic/performance/graphics/load-bitmap#java
Which gives a solution to your answer explained properly and another way is to change the size of the image which can be found here and then use it accordingly
https://developer.android.com/reference/android/graphics/drawable/ScaleDrawable
I am currently working on an Android App in which I have to send my Image to MySql server using php
I am using BLOB type field in which it restricts me from saving big images. It allows only 64KiB which is not less but is causing problem for big images.
I don't want any good quality but just wanna save it. I compress it using Bitmap.compress method. I am using it with blind faith and don't know if it works well or not.
Here is my converting method:
public Bitmap compress(Bitmap bitmap){
Bitmap original =bitmap;
ByteArrayOutputStream out = new ByteArrayOutputStream();
original.compress(Bitmap.CompressFormat.JPEG, 30, out);
Bitmap decoded = BitmapFactory.decodeStream(new ByteArrayInputStream(out.toByteArray()));
Log.e("Original dimensions", original.getWidth() + " " + original.getHeight());
Log.e("Compressed dimensions", decoded.getWidth() + " " + decoded.getHeight());
return decoded;
}
Thanks in Advance
try this:
/**
* reduces the size of the image
* #param image
* #param maxSize
* #return
*/
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);
}
use it like:
Bitmap scaledImage = getResizedBitmap(photo, 200); //here 200 is maxsize
From Wikipedia
For highest quality images (Q=100), about 8.25 bits per color pixel is required
So, for Q=100 on an 200x200 image, that would result in (200 * 200) px * 8.25 bits/px = 330000 bits = ~ 41 kB which is surely less than 64KB
you can try for other dimension too..
you can also try to make an image using the resized bitmap and compare the actual size of the image..
here is the code:
//create a file to write bitmap data
File f = new File(context.getCacheDir(), filename);
f.createNewFile();
//Convert bitmap to byte array
Bitmap bitmap = your bitmap;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
byte[] bitmapdata = bos.toByteArray();
//write the bytes in file
FileOutputStream fos = new FileOutputStream(f);
fos.write(bitmapdata);
fos.flush();
fos.close();
Alternatively, you can use the Compressor library to compress the images with more configurable options.
You can add the library to your app from gradle:
dependencies {
compile 'id.zelory:compressor:1.0.4'
}
And generate a compressed image by :
compressedImage = new Compressor.Builder(this)
.setMaxWidth(640)
.setMaxHeight(480)
.setQuality(75)
.setCompressFormat(Bitmap.CompressFormat.WEBP)
.setDestinationDirectoryPath(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).getAbsolutePath())
.build()
.compressToFile(actualImage);
I am trying to find a solution to be able to download an image from Url into the Filesystem and resize it without the need to decode it to a bitmap first.
The only solution I could find yet was the approach to decode it to a bitmap with specific SampleSize. However, the problem here is that a lot of memory is consumed when creating the bitmap which would limit the maximum size of the image depending on the free memory available on the device. Another Problem is that it takes a lot of time to decode it to a Bitmap and also to compress it back to an output stream.
My current code looks like this (options1 contains the original dimensions, calculateSampleSizeForImageSize() calculates the sampleSize for smaller required Imagesize):
if (maximumImageSize < Runtime.getRuntime().maxMemory() / 4f) {
//enough memory free to reduce size
//reduce size
BitmapFactory.Options options2 = new BitmapFactory.Options();
options2.inSampleSize = calculateSampleSizeForImageSize(options1, maximumImageSize);
options2.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeStream(is, null, options2);
try {
//write reduced size bitmap to picture file
os = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
os.flush();
bitmap.recycle();
return 0;
} catch (IOException e) {
Log.d(TAG, "failed to fetch image", e);
throw new IOException(e);
} finally {
closeStream(os);
closeStream(is);
}
So is there a way to bypass the bitmap and directly write the input stream in reduced size to the output stream?
I have this method here, which converts an image to a byte array.
public byte[] imageToCompressedByteArray(Image image) throws IOException {
//load the image
String f = "C:\\Users\\mamed\\Documents\\NetBeansProjects\\Main\\src\\resources\\accept.png";
image = ImageIO.read(new FileInputStream(new File(f)));
// get image size
int width = image.getWidth(null);
int height = image.getHeight(null);
try {
int[] imageSource = new int[width * height];
PixelGrabber pg = new PixelGrabber(image, 0, 0, width, height, imageSource, 0, width);
pg.grabPixels();
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
GZIPOutputStream zippedStream = new GZIPOutputStream(byteStream);
ObjectOutputStream objectStream = new ObjectOutputStream(zippedStream);
objectStream.writeShort(width);
objectStream.writeShort(height);
objectStream.writeObject(imageSource);
objectStream.flush();
objectStream.close();
return byteStream.toByteArray();
}
catch (Exception e) {
throw new IOException("Error storing image in object: " + e);
}
}
However, i can't get this to work, i mean, it can't load the image and convert it, and i don't have an idea what the problem can be.
Are you sure the image path is correct and the loaded image is not corrupted image.
I not modified your code and I can see 1778416 byes its read from the image file.
I do not see anything wrong with the program. Maybe your image file is corrupted or image path is not correct.
The Encode source i have to put these Blobs (images) in my database is
Bitmap image15 = BitmapFactory.decodeResource(getResources(),R.drawable.wolverineklein);
// convert bitmap to byte
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image15.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte imageInByte1[] = stream.toByteArray();
And this is the code to get the image back from the SQLite database
DatabaseHandler db = new DatabaseHandler(camera.this);
List<Database> contacts = db.getAllContacts();
for (Database contact : contacts) {
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
//bmpFactoryOptions.inScaled = false;
// decodes the blob back into a bitmap
byte[] blob = contact.getMP();
ByteArrayInputStream inputStream = new ByteArrayInputStream(blob);
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
Bitmap scalen = Bitmap.createScaledBitmap(bitmap, 320, 240, false);
The code works fine, but the output of the images is of a lower quality (the difference isn't that big, but i use it for feature matching so it does impact the results. The right output image is more blurry then the )
As you can see, the right image is the output and it is more bit-like (check out the upper chest area and compare it to the left input image) then the original + the size is bigger. Is there a possibility to make the quality better then I have it right now?