Trying to scale down a Bitmap in Android not working - java

I'm trying to scale down a bitmap. In short, the image is originally from a ByteArray with a width of 4016. After I scale the image down with factory options, it still reports the image with a width of 4016.
Here are two clips of my code:
Bitmap myBitmap = null;
#Override
protected byte[] doInBackground(Object... params) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
if (options.outHeight > options.outWidth) {
options.inSampleSize = calculateInSampleSize(options, 640, 960);
} else {
options.inSampleSize = calculateInSampleSize(options, 960, 640);
}
options.inJustDecodeBounds = false;
//myImageByteArray is 4016 wide
myBitmap = BitmapFactory.decodeByteArray(myImageByteArray, 0, myImageByteArray.length, options);
//This log statement outputs 4016!!! Shouldn't it be smaller since I just decoded the byteArray with options?
Log.d("bitmap", myBitmap.getWidth()+"");
}
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and
// width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will
// guarantee
// a final image with both dimensions larger than or equal to
// the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
Update:
Here are two clips of my code:
Bitmap myBitmap = null;
#Override
protected byte[] doInBackground(Object... params) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
//myImageByteArray is 4016 wide
myBitmap = BitmapFactory.decodeByteArray(myImageByteArray, 0, myImageByteArray.length, options);
if (options.outHeight > options.outWidth) {
options.inSampleSize = calculateInSampleSize(options, 640, 960);
} else {
options.inSampleSize = calculateInSampleSize(options, 960, 640);
}
options.inJustDecodeBounds = false;
//myImageByteArray is 4016 wide
myBitmap = BitmapFactory.decodeByteArray(myImageByteArray, 0, myImageByteArray.length, options);
//This log statement outputs around 1000 now.
Log.d("bitmap", myBitmap.getWidth()+"");
}
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and
// width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will
// guarantee
// a final image with both dimensions larger than or equal to
// the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}

You need to call .decodeByteArray(..) twice! Once to attain the width and the height with .inJustDecodeBounds set to true and then again with .inSampleSize to get the actual scaled Bitmap, options.outHeight and options.outWidth in your code are probably zero.
call BitmapFactory.decodeByteArray(myImageByteArray, 0, myImageByteArray.length, options); before checking outHeight and outWidth.
Edit
Take a look at this example from Google's Android Dev site:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
Notice that options.outHeight is used after calling decodeResources(...). This you have to fix in your code.

you didn't fill the bitmapOptions with anything that will set its "outHeight" and "outWidth".
you should check out the bitmap tutorials of google:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
in order to decide what is the value to scale to, you need to know the size of the image compared to the destination size. that's why you need to do decoding twice.
btw, there is another way to downsample images, as i've shown here.

Related

Bitmap taking too much time to set as Wallpaper

when I want to set the image as wallpaper it takes more than 5seconds or even more on some occasions causing the app crash.
I have done a lot of searches and found the way to use setStream in place of setBitmap but actually didn't work as there is no full code mentioned by anyone.
so please help me to set the wallpaper quickly.
My Code:
private void setAsWallpaper(Bitmap bitmap) {
mNotifyManager.cancel(id);
WallpaperManager wallpaperManager = WallpaperManager.getInstance(getApplicationContext());
try {
wallpaperManager.setBitmap(bitmap);
success();
} catch (IOException e) {
e.printStackTrace();
failure();
}
}
You can use below functions to resize bitmap-
fun decodeSampledBitmapFromFilePath(filepath: String, reqWidth: Int, reqHeight: Int): Bitmap? {
// First decode with inJustDecodeBounds=true to check dimensions
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(filepath, options)
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
if(options.outHeight == 0 && options.outWidth == 0){
return null
}
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false
return BitmapFactory.decodeFile(filepath, options)
}
/**
* Method to calculate a sample size value that is a power of two based on a target width and height.
* #param options
* #param reqWidth
* #param reqHeight
* #return sampleSize
*/
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
// Raw height and width of image
val height = options.outHeight
val width = options.outWidth
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
/* val halfHeight = height / 2
val halfWidth = width / 2
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}*/
if (width > height) {
inSampleSize = Math.round(width.toFloat() / reqWidth.toFloat())
} else {
inSampleSize = Math.round(height.toFloat() / reqHeight.toFloat())
}
}
return inSampleSize
}
Resize your bitmap according to screen size
it will reduce time to set
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
int screenWidth = displayMetrics.widthPixels;
bitmap = Bitmap.createScaledBitmap(bitmap,screenWidth, screenHeight, true);
Now you can set this bitmap for wallpaper

How to reduce image without losing quality and preserving the aspect ratio? [duplicate]

This question already has answers here:
Strange OutOfMemory issue while loading an image to a Bitmap object
(44 answers)
Closed 7 years ago.
I have image 3264х2448.
I load it in bitmap crudeImage = BitmapFactory.decodeFile(picturePath); then set to the ImageView imageCrop.setImageBitmap(crudeImage);
the problem is that it very big(3264х2448) and my library cropper
work very slowly with this image.
I want reduce the image to a smaller size. but to preserve the quality and proportion. how can this be done?
You should read Loading Large Bitmaps Efficiently.
Essentially, calculate the sample size first by
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
then load the image using the sample size:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
I use this to resize my bitmap's. Maybe can be useful for you.
public Bitmap resizeBitmap(Bitmap bitmap, int newWidth, int newHeight)
{
//bitmap it's the bitmap you want to resize
//newWidth and newHeight are the with and height that you want to the new bitmap
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) /height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
I hope this help you.
If you read the tutorial here you'll see google gives you a very comprehensive guide on how to scale bitmaps without excessive memory usage.
In particular, these two methods (from the tutorial):
public static Bitmap decodeSampledBitmapFromResource(File file, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(file, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight){
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Will accomplish your scaling whilst preserving aspect ratio.
The trick with these two methods is that the first decode operation is set to only decode the image's properties (dimensions and other information) as set by:
options.inJustDecodeBounds = true;
in which the new scale factor is calculated proportionally by the method calculateInSampleSize().
Then the image is decoded again, this time with
options.inJustDecodeBounds = false;
as well as the scale factor set, so the bitmap factory automatically scales whilst decoding, reducing the memory footprint.
Regarding what resolution the image needs to be decoded to, that really depends on your use-case. But, if the particular image does not need to be zoomed in later, you need no more than the device's physical screen size, which can be obtained via:
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getRealSize(size);
in which object size will hold the device's screen size.

How to set image as wallpaper without causing OutOfMemory error

I have a straight-forward wallpaper app which I've just finished coding. I've used the tutorial here ( http://developer.android.com/training/displaying-bitmaps/load-bitmap.html ) to load the image on the screen at a lower resolution, while I use the code below to set the wallpaper. imageId represents the currently selected drawable.
InputStream is = getResources().openRawResource(imageId);
Bitmap bitmap = BitmapFactory.decodeStream(is);
WallpaperManager myWallpaperManager = WallpaperManager
.getInstance(getApplicationContext());
try {
myWallpaperManager.setBitmap(bitmap);
Toast.makeText(MainActivity.this, "Teamwork! Wallpaper set.",
Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(
MainActivity.this,
"Damnit, clickers! Wallpaper wasn't set. Try killing and restarting the app?",
Toast.LENGTH_LONG).show();
}
However, I fear that the above code may cause phones of less memory to crash when it is called. Is there anything I can do to prevent this from happening, or is there nothing to fear?
EDIT:
I used this code to display the images in the app:
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public void setImage() {
switch (imageNum) {
case 1:
options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.aaaae,
options);
display.setImageBitmap(decodeSampledBitmapFromResource(
getResources(), R.drawable.aaaae, 300, 300));
imageId = R.drawable.aaaae;
break;
// Etc, etc.
}
}
The method setImage is called every time the "Next" button is pressed.
I guess you are not handling images properly
Here is the solution to handle bitmaps in android
(This approach can handle 25000px by 25000px images in a non-ui thread.)
First, you should calculate the sampleBitmapSize of the bitmap ( to load the lower version of the same bitmap )
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
Below is the util function to calculate InSampleSize ( an integer that defines the value (rating) of quality of a bitmap).
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
Log.i("ImageUtil", "InSampleSize: "+inSampleSize);
return inSampleSize;
}

Android OutOfMemoryError for large images

The method throws OutOfMemoryError for large images in size( not by resolution)
i have 12 MP photos, all of them are different in size(1.5MB, 2.5MB, 3.2MB, 4.1MB) etc and the resolution for all of them are same 4000 x 3000 (pixels).
The resizer method works fine for images less than 3MB in size, but for those images that are >3MB it throws OutOfMemoryError i dont know what could fix it, my app is mainly for resizing large images, and this is really getting me nowhere.
The resizer method is:
public File resizeBitmap(String imPath, int reqSize) {
File fl = null;
try{
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imPath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqSize);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap saveImg = BitmapFactory.decodeFile(imPath, options);
//save resized image
String tmpName = "IMG_"+String.valueOf(System.currentTimeMillis()) + ".jpg";
fl = fileCache.getRawFile(tmpName);
FileOutputStream fos = new FileOutputStream(fl);
saveImg.compress(Bitmap.CompressFormat.JPEG, 95, fos);
saveImg.recycle();
return fl;
}
catch (Throwable eex){
eex.printStackTrace();
if(eex instanceof OutOfMemoryError) {
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(Resizer.this, "Memory ran out", Toast.LENGTH_SHORT).show();
}
});
}
return null;
}
}
//Method for calculating insamplesize
public int calculateInSampleSize(BitmapFactory.Options options, int reqSize) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
Log.i("width",""+width);
Log.i("height",""+height);
int inSampleSize = 1;
if (height > reqSize || width > reqSize) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqSize);
} else {
inSampleSize = Math.round((float) width / (float) reqSize);
}
}
return inSampleSize;
}
//Stacktrace
04-11 09:01:19.832: W/System.err(8832): java.lang.OutOfMemoryError
04-11 09:01:19.953: W/System.err(8832): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
04-11 09:01:19.972: W/System.err(8832): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:529)
04-11 09:01:19.993: W/System.err(8832): at com.scale.app.Resizer.resizeBitmap(Resizer.java:1290)
Read Article
Image Size : 4000*3000 in px
When the image load : 4000*3000*4 = ? KB
So,Vitrual Heap Memory of the Android device are : 32 MB, 64 MB , 128 MB ... so on
if you using:
<application
android:largeHeap="true">
</application>
This will increase the VHM double (if 32 MB = 2* 32 MB). BUt this will not an good way to do this, effect on OS
You need to decrease the size of the image.
Use the below class and pass the path of the image and width , height what you want
Bitmap bitmap = BitmapSize.getDecodedBitmap(path, 400, 400);
Class::::
public class BitmapSize{
public static Bitmap getDecodedBitmap(String path, float target_width, float target_height) {
Bitmap outBitmap = null;
try {
Options decode_options = new Options();
decode_options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path,decode_options); //This will just fill the output parameters
int inSampleSize = calculateInSampleSize(decode_options, target_width, target_height);
Options outOptions = new Options();
outOptions.inJustDecodeBounds = false;
outOptions.inSampleSize = inSampleSize;
outOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
outOptions.inScaled = false;
Bitmap decodedBitmap = BitmapFactory.decodeFile(path,outOptions);
outBitmap = Bitmap.createScaledBitmap(decodedBitmap,// (int)target_width, (int)target_height, true);
(int)((float)decodedBitmap.getWidth() / inSampleSize),
(int)((float)decodedBitmap.getHeight() / inSampleSize), true);
System.out.println("Decoded Bitmap: Width " + outBitmap.getWidth() + " Height = " + outBitmap.getHeight() + " inSampleSize = " + inSampleSize);
} catch (Exception e) {
// TODO: handle exception
}
return outBitmap;
}
public static int calculateInSampleSize(Options options, float reqWidth, float reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height
/ (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
}
Please read http://developer.android.com/training/displaying-bitmaps/load-bitmap.html how to deal with large Bitmaps.
The BitmapFactory class provides several decoding methods
(decodeByteArray(), decodeFile(), decodeResource(), etc.) for creating
a Bitmap from various sources. Choose the most appropriate decode
method based on your image data source. These methods attempt to
allocate memory for the constructed bitmap and therefore can easily
result in an OutOfMemory exception
Please try with these Option properties..
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inDither=false; //Disable Dithering mode
opts.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
opts.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
opts.inTempStorage=new byte[32 * 1024];
opts.inSampleSize = 1;
To reduce the quality of image file increase the number of opts.inSampleSize. Click here for more details
And add one more attribute in Manifest.xml in application tag
android:largeHeap="true"

Implementation of BitmapFactory.Options for Android

I have 60 images that I use for my app. Each image is only (approx) 25KB in size. I use single letter naming convention for upper case, and double letters for lower case.
a.png
aa.png
In my layout I have 10 ImageViews, and I am programatically setting the image bitmap according to a word which I pull from a database.
My problem is, I may not have implemented the down sampling correctly. I add all 10 ImageViews to an ImageView array, and then find them to set their bitmap value based on the character array of the word from the database. Here is my method to set the Image View:
for(int j = 0; j < myWord.length(); j++){
char[] chars = myWord.toCharArray();
if(Character.isUpperCase(chars[j])){
int imgID = getResources().getIdentifier(String.valueOf(chars[j]).toLowerCase(), "drawable", getPackageName());
letters[j].setTag(String.valueOf(chars[j]).toUpperCase());
letters[j].setImageBitmap(CalculateSize.decodeSampledBitmapFromResource(getResources(), imgID, 75, 75));
}else if(Character.isLowerCase(chars[j])){
int imgID = getResources().getIdentifier(String.valueOf(chars[j]) + String.valueOf(chars[j]), "drawable", getPackageName());
letters[j].setTag(chars[j] + chars[j]);
letters[j].setImageBitmap(CalculateSize.decodeSampledBitmapFromResource(getResources(),imgID, 75, 75));
}
Here are my methods to resize. I got it working for a 3 letter word with an inSampleSize = 4 setting. However, I have been unable to get it working again no matter what I set it to:
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight){
// Height and Width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 12;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose smallest ratio as inSampleSize value.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resID, int reqWidth, int reqHeight){
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resID, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resID, options);
}
}
Ultimately I think the answer is to just use even smaller images.
I added debug logging to my code, which gave me the height and width of each image before it was sampled, and then it gave me the smallest ratio that was chosen.
Each image worked out to 930 x 1110 dp, and I was trying to sample it at 1/12th of that size, which was still growing the heap to 42MB + before crash.
I think what I need to do is resize the images to maybe 1/4 of the size they are now and downsample those by half, or even 1/4.
I can update this question for the 8 of you that read it to let you know if that works.
Edit: Updated the original size of the images and this works flawlessly.

Categories