April 7, 2010

Bitmap Format Woes

While I was trying to learn about the AvoidXfermode class, I had some issues with my images not being displayed with the correct colors. I had created a smooth gradient color spectrum image, and was testing the AvoidXfermode's "Target" functionality to replace an exact color in the image with a different one. The problem was that the color I was targetting was not being replaced by the AvoidXfermode in the way that I expected. It turned out that my PNG image was being automatically converted from a 24-bit RGB888 image to a 16-bit RGB565 image, which was causing the colors to be altered from their original values.

Almost all mobile devices in the market today have 16-bit color screens, which means that no matter what format of image you work with in your code, at some point it will be converted to a 16-bit image so it can be displayed on the screen. This conversion takes processing power, which costs the user in the form of time and battery life. Therefore it is undesirable for this conversion to take place when images are drawn, and instead should be done as early as possible in the life-cycle of the image. Android is a clever system, and there are certain situations where Android automatically does this conversion for you so that you never have to worry about it. Most of the time this is great, and simplifies the amount of work developers need to do, while cutting down the size of your resources and saving processing time and battery life. However, if you need your images to be in a specific format for processing in an Android app, this automatic conversion can cause some headaches. Luckily there are ways to prevent it from happening, so your images remain in exactly the format that you need them to be.

But if this automatic conversion saves memory, processing time, and battery life, why would anyone not want this conversion to occur? Most images in an Android app are simply loaded and displayed. But in the tutorials and examples I post here, we will be doing a decent amount of manipulation to the images before they are displayed. To get the best results, we want the images we're working with to be of the highest quality possible while we adjust them, because any reduction in quality early on in our process will compound itself and greatly affect the end result.

All right, so we don't want our images to be messed with by Android until we're good and ready. When and where will Android try to convert them for us, and how do we prevent it? There are three places where this conversion can take place:
  1. When resources are compiled into your app's package at build time
  2. When the image is loaded from resources into a Bitmap
  3. When the image is drawn
Let's take a look at each of these situations, and how to get around each of them.

During Application Build Time
When you place an image resource into the "res/drawable" folder of your project, you are secretly telling Android that it is okay for this image to be converted to a 16-bit format when your project is built, if Android deems it necessary. What conditions need to be met for this conversion to take place here? Regardless of the original format of your image resource, if the image does not have an alpha channel, Android will convert it to the native 16-bit format when your app is built. There are two ways we can keep this from happening here:
  1. Give the image an alpha channel
  2. Place the image into the "res/raw" directory instead of the "res/drawable" directory
By giving the image an alpha channel, Android will not try to convert it to a 16-bit image, because the 16-bit RGB565 format has no alpha channel. Converting an image with an alpha channel to an RGB565 format would cause that alpha information to be lost, so any image with an alpha channel will be stored as a 32-bit ARGB8888 image resource.

By storing the image in the "res/raw" directory, we are telling Android that this resource contains raw data that should not be altered at build time. Therefore, any format conversion will not take place on any image we place in this directory. Unfortunately, we will still have a problem with this method if the image we put in the "raw" directory has no alpha channel. This will become apparent when we try to load the image:

On Image Load
When loading an image from your application's resources into a Bitmap via the BitmapFactory class, the same automatic conversion process attempts to take place. If the image you are loading does not have an alpha channel, Android will convert it to a 16-bit RGB565 bitmap. Unfortunately, this happens even if we place our image in the "res/raw" directory instead of the "res/drawable" directory. According to this post by Romain Guy, one of the developers of Android, there is a workaround:

What you need is to decode the image directly in ARGB 8888. When you call one of the BitmapFactory.decode*() methods, you can specify a set of BitmapFactory.Options. Simply pass a set of options in which inDither == false. This will tell BitmapFactory to not attempt to dither the original 24 bits image to 16 bits.

However, I have found that this does not work, at least not in Android 1.6. Passing in a set of options in which inDither == false will indeed cause the resultant bitmap not to be dithered, but it does not prevent the conversion from taking place at all. The image will still be converted to 16-bit RGB565, but this will simply cause the converted bitmap to have banding artifacts if there are any gradients in the image.

Since this recommended workaround does not produce the results we are looking for, the only solution to preserving your image's full fidelity in this stage of the process is to have an image with an alpha channel. When the BitmapFactory sees that the image resource has an alpha channel, it will have no choice but to decode that image into a 32-bit ARGB8888 bitmap.

On Draw
Let's say we have two bitmaps. Bitmap A is an ARGB8888 bitmap, and Bitmap B is an RGB565 bitmap. If we want to draw Bitmap A onto Bitmap B, Bitmap A will need to be converted to Bitmap B's format. Luckily, this conversion is taken care of for us in Canvas's drawBitmap method. This is great news for us, because this is exactly when we want this conversion to happen. By the time we're ready to finalize our bitmap, we will have completed all of our adjustments and manipulations, and it will be ready to be converted and displayed. However, since we're converting down from 32 to 16 bits of color depth, this will cause a loss of fidelity. In order to limit the impact that this loss of fidelity has on our image, we have some control over how the conversion from 32 to 16 bits takes place. By default, no dithering is performed when drawing a higher bit-depth bitmap to a lower bit-depth one. For images with smooth gradients, this can result in an artifact known as "banding," and it is quite unsightly. To avoid this problem, we can tell Android that we want to dither the result of the conversion. Dithering is the process of introducing very slight variations in color to trick our eyes into seeing a smooth gradient in low bit-depth images. We'll see how to do this in the examples below.

The important thing to remember here is that all of our Bitmap objects should be in a 32-bit ARGB8888 format while we are working with them. Only when we are done altering the images should they be allowed to be converted to 16-bit RGB565. Due to the fact that any image without an alpha channel will be converted to RGB565 when loaded by BitmapFactory, regardless of whether or not it is placed in "res/drawable" or "res/raw", the only way to ensure that your images get decoded correctly into 32-bit ARGB8888 bitmaps is to make sure that your images have an alpha channel.

Example
To demonstrate this, let's do a few simple tests. First lets make an activity that just loads a few images and displays them with the default options. Both images will be a smoothly graded color spectrum, but they will be saved in different formats. The first will be saved as a 24-bit RGB888 PNG, and the second will be saved as a 32-bit ARGB8888 PNG with an alpha channel. Both images have exactly the same color data, the only difference is that one is saved with an alpha channel, the other is not. Go ahead and save both of these images and place them in your project's "res/raw" directory. Then, use the following code for your project's activity:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Load both of our images from our application's resources.
    Resources r = getResources();
    Bitmap resource24 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_24);
    Bitmap resource32 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_32);

    // Print some log statements to show what pixel format these images were decoded with.
    Log.d("FormatTest","Resource24: " + resource24.getConfig()); // Resource24: RGB_565
    Log.d("FormatTest","Resource32: " + resource32.getConfig()); // Resource32: ARGB_8888

    // Create two image views to show these bitmaps in.
    ImageView image24 = new ImageView(this);
    ImageView image32 = new ImageView(this);
    image24.setImageBitmap(resource24);
    image32.setImageBitmap(resource32);

    // Create a simple layout to show these two image views side-by-side.
    LayoutParams wrap = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    LayoutParams fill = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
    RelativeLayout.LayoutParams params24 = new RelativeLayout.LayoutParams(wrap);
    params24.addRule(RelativeLayout.CENTER_VERTICAL);
    params24.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
    RelativeLayout.LayoutParams params32 = new RelativeLayout.LayoutParams(wrap);
    params32.addRule(RelativeLayout.CENTER_VERTICAL);
    params32.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
    RelativeLayout layout = new RelativeLayout(this);
    layout.addView(image24, params24);
    layout.addView(image32, params32);
    layout.setBackgroundColor(Color.BLACK);

    // Show this layout in our activity.
    setContentView(layout, fill);
}

Build this and push it to your device to see the results. We see our 24 bit image on the left, and the 32 bit image on the right. But wait a minute, the one on the left looks better than the one on the right! The 32-bit image appears to have some slight banding artifacts, but the 24-bit image looks nice and smooth. What is going on here? Let's take a closer look at the image, and we can see:


Upon close inspection, we can see that the 24-bit image has been dithered, and the 32-bit image has not. Remember that when any image is displayed on the screen in Android, it will be converted to the native 16-bit format of the screen, as both of these images have been. Since the 24-bit image doesn't have an alpha channel, and it was placed in the "res/raw" directory, it was automatically converted to a 16-bit RGB565 format when we loaded it from the resources in our activity. We can verify this by checking our application's logcat messages. The Bitmap object we received from BitmapFactory after loading our 24-bit image is indeed an RGB565 bitmap, and BitmapFactory was even kind enough to dither the image for us. Our 32-bit image however, which has an alpha channel, was decoded as an ARGB8888 bitmap. It was converted to the native 16-bit screen format when its corresponding ImageView was drawn to the screen. What we've seen here is the default behavior of not dithering the image when converting on a draw. Let's see if we can make this look a little better by specifying that we want the conversion to be dithered. Add the following few lines of code at line 19 in the above example:

// Enable dithering when our 32-bit image gets drawn.
Drawable drawable32 = image32.getDrawable();
drawable32.setDither(true);

Build and push this to your device and take a look at the results:


Ahh, there we go. Now our 32-bit image is nice and dithered as well. Here we simply told our ImageView that we wanted its bitmap to be dithered any time it is drawn to the screen. Unfortunately this can slow down the drawing speed, so it is a less than ideal situation for us. We can eliminate this performance hit though, by pre-dithering our image before we send it to the ImageView:
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Load both of our images from our application's resources.
    Resources r = getResources();
    Bitmap resource24 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_24);
    Bitmap resource32 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_32);

    // Print some log statements to show what pixel format these images were decoded with.
    Log.d("FormatTest","Resource24: " + resource24.getConfig()); // Resource24: RGB_565
    Log.d("FormatTest","Resource32: " + resource32.getConfig()); // Resource32: ARGB_8888

    // Save the dimensions of these images.
    int width = resource24.getWidth();
    int height = resource24.getHeight();

    // Create a 16-bit RGB565 bitmap that we will draw our 32-bit image to with dithering.
    Bitmap final32 = Bitmap.createBitmap(width, height, Config.RGB_565);

    // Create a new paint object we will use to draw our bitmap with. This is how we tell
    // Android that we want to dither the 32-bit image when it gets drawn to our 16-bit final
    // bitmap.
    Paint ditherPaint = new Paint();
    ditherPaint.setDither(true);

    // Create a new canvas for our 16-bit final bitmap, and draw our 32-bit image to it with
    // the paint object we just created.
    Canvas canvas = new Canvas();
    canvas.setBitmap(final32);
    canvas.drawBitmap(resource32, 0, 0, ditherPaint);

    // Create two image views to show these bitmaps in.
    ImageView image24 = new ImageView(this);
    ImageView image32 = new ImageView(this);
    image24.setImageBitmap(resource24);
    image32.setImageBitmap(final32);

    // Create a simple layout to show these two image views side-by-side.
    LayoutParams wrap = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    LayoutParams fill = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
    RelativeLayout.LayoutParams params24 = new RelativeLayout.LayoutParams(wrap);
    params24.addRule(RelativeLayout.CENTER_VERTICAL);
    params24.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
    RelativeLayout.LayoutParams params32 = new RelativeLayout.LayoutParams(wrap);
    params32.addRule(RelativeLayout.CENTER_VERTICAL);
    params32.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
    RelativeLayout layout = new RelativeLayout(this);
    layout.addView(image24, params24);
    layout.addView(image32, params32);
    layout.setBackgroundColor(Color.BLACK);

    // Show this layout in our activity.
    setContentView(layout, fill);
}

Running this code on your device will produce the same visual result as if we had set our ImageView to dither on draw, but here we have eliminated the performance hit by pre-dithering our image to a separate bitmap before displaying it. This is not really necessary if you are simply loading and displaying your images once in your activity, but any bitmaps that are drawn frequently should be pre-dithered to avoid wasting processing time and battery life.

At this point you may be wondering why we've gone through all of this work to make sure our image gets stored and decoded as a full 32-bit bitmap, only to have to do even more work to make sure it gets dithered correctly when we convert it back down to the native 16-bit depth that Android likes. What is the point of all of this, especially when the 24-bit image that got automatically converted to 16 bits looks great, with no work done on our part? Well, in the examples thus far, we haven't actually done any processing or manipulating of these bitmaps yet, so the 24-bit image that got automatically converted looks just as good as the 32-bit image that we converted manually. They both end up being just about the exact same image in the end. But what happens if we want to do some processing of the image before we display it? Let's find out. Try out this code on your device:
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Load both of our images from our application's resources.
    Resources r = getResources();
    Bitmap resource24 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_24);
    Bitmap resource32 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_dithered_32);

    Log.d("FormatTest","Resource24: " + resource24.getConfig()); // Resource24: RGB_565
    Log.d("FormatTest","Resource32: " + resource32.getConfig()); // Resource32: ARGB_8888

    // Sadly, the images we have decoded from our resources are immutable. Since we want to
    // change them, we need to copy them into new mutable bitmaps, giving each of them the same
    // pixel format as their source.
    Bitmap bitmap24 = resource24.copy(resource24.getConfig(), true);
    Bitmap bitmap32 = resource32.copy(resource32.getConfig(), true);

    // Save the dimensions of these images.
    int width = bitmap24.getWidth();
    int height = bitmap24.getHeight();

    // Create a new paint object that we will use to manipulate our images. This will tell
    // Android that we want to replace any color in our image that is even remotely similar to
    // 0xFF307070 (a dark teal) with 0xFF000000 (black).
    Paint avoid1Paint = new Paint();
    avoid1Paint.setColor(0xFF000000);
    avoid1Paint.setXfermode(new AvoidXfermode(0xFF307070, 255, AvoidXfermode.Mode.TARGET));

    // Make another paint object, but this one will replace any color that is similar to a
    // 0xFF00C000 (green) with 0xFF0070D0 (skyish blue) instead.
    Paint avoid2Paint = new Paint();
    avoid2Paint.setColor(0xFF0070D0);
    avoid2Paint.setXfermode(new AvoidXfermode(0xFF00C000, 245, AvoidXfermode.Mode.TARGET));

    Paint fadePaint = new Paint();
    int[] fadeColors = {0x00000000, 0xFF000000, 0xFF000000, 0x00000000};
    fadePaint.setShader(new LinearGradient(0, 0, 0, height, fadeColors, null,
            LinearGradient.TileMode.CLAMP));
    fadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

    // Create a new canvas for our bitmaps, and draw a full-sized rectangle to each of them
    // which will apply the paint object we just created.
    Canvas canvas = new Canvas();
    canvas.setBitmap(bitmap24);
    canvas.drawRect(0, 0, width, height, avoid1Paint);
    canvas.drawRect(0, 0, width, height, avoid2Paint);
    canvas.drawRect(0, 0, width, height, fadePaint);
    canvas.setBitmap(bitmap32);
    canvas.drawRect(0, 0, width, height, avoid1Paint);
    canvas.drawRect(0, 0, width, height, avoid2Paint);
    canvas.drawRect(0, 0, width, height, fadePaint);

    // Create a 16-bit RGB565 bitmap that we will draw our 32-bit image to with dithering. We
    // only need to do this for our 32-bit image, and not our 24-bit image, because it is
    // already in the RGB565 format.
    Bitmap final32 = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);

    // Create a new paint object that we will use to draw our bitmap with. This is how we tell
    // Android that we want to dither the 32-bit image when it gets drawn to our 16-bit final
    // bitmap.
    Paint ditherPaint = new Paint();
    ditherPaint.setDither(true);

    // Using our canvas from above, draw our 32-bit image to it with the paint object we just
    // created.
    canvas.setBitmap(final32);
    canvas.drawBitmap(bitmap32, 0, 0, ditherPaint);

    // Create two image views to show these bitmaps in.
    ImageView image24 = new ImageView(this);
    ImageView image32 = new ImageView(this);
    image24.setImageBitmap(bitmap24);
    image32.setImageBitmap(final32);

    // Create a simple layout to show these two image views side-by-side.
    LayoutParams wrap = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    LayoutParams fill = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
    RelativeLayout.LayoutParams params24 = new RelativeLayout.LayoutParams(wrap);
    params24.addRule(RelativeLayout.CENTER_VERTICAL);
    params24.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
    RelativeLayout.LayoutParams params32 = new RelativeLayout.LayoutParams(wrap);
    params32.addRule(RelativeLayout.CENTER_VERTICAL);
    params32.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
    RelativeLayout layout = new RelativeLayout(this);
    layout.addView(image24, params24);
    layout.addView(image32, params32);
    layout.setBackgroundColor(Color.BLACK);

    // Show this layout in our activity.
    setContentView(layout, fill);
}

Here we've loaded the same two images as in the previous examples, but instead of simply adding them to an ImageView and displaying them, we've done some image processing on them first with Android's built-in graphics functionality. We ran each image through three different filters to alter the colors before we displayed them in an ImageView. Don't worry if you don't understand what the lines of code from 23-52 do right now, I will be explaining these and much more in later posts. For now, just know that our original images have been heavily altered by our application at run-time, but before they were displayed. Here is what the final version of each image looks like when it finally gets to the screen:


As you can see, the 24-bit image has a considerable amount of distortion as a result of the operations we've performed on it, whereas the 32-bit image is nice and smooth. In addition to the banding and distortion, you can see that the actual colors in the 24-bit image are simply incorrect. So why did this happen to the 24-bit image, but not the 32-bit one? The answer lies in the fact that the 24-bit image was automatically converted down to a 16-bit RGB565 format by Android. A 16-bit image can only display 65,535 different colors, whereas a 32-bit image can display 16,777,215 colors, with 255 shades of transparency for each and every one of those colors. This means that when we do anything to alter a 16-bit image, the resulting colors may not be able to be accurately represented by the range of 65,535 colors that 16-bit images support. Therefore the resulting colors get truncated to the nearest possible color that can be represented in the 16-bit color spectrum. Every time we alter the image, this truncation happens again, so the more we alter the image the less accurate our results will be. When working with 32-bit images, this phenomenon still occurs, but with almost 17 million colors to work with in the spectrum, the side-effects are not noticeable for most applications.

I hope you found this post useful. The main ideas I hope you to take away from this are:
  • Beware of Android's automatic image conversion and dithering. Knowing how Android is storing, loading, and drawing your images can alleviate a lot of headaches when things aren't working the way you want them to.
  • If you're going to perform any kind of alteration of your images after they are loaded, make sure you are working with 32-bit images in the ARGB8888 format.
  • You can force an image to be packed and decoded as a 32-bit ARGB8888 image by saving it as a 32-bit PNG with an alpha channel in your graphics program of choice.
  • After you're done altering your 32-bit image, remember to convert it to a 16-bit RGB565 image with dithering enabled.

If you have any questions or comments about any of this, feel free to post a comment and I'll see if I can help!

18 comments:

  1. Keep up the good work, this was a nice read!

    ReplyDelete
  2. Hi!
    I am trying to draw bitmap with some gray gradients, but it always come up with some serious banding. I tried to use your code in test project and replaced one of bitmaps with my own, but problem remains. I have alpha channel in my png and it is placed in raw folder but it just doesn't work.
    I had bit hard time making png with alpha channel (as photoshop doesn't support it directly) so maybe there is problem, how did you make png with alpha channel? Could you please post steps hwo to create bitmap with alpha channel that works?

    ReplyDelete
  3. good work, keep it up,waiting for new posts.

    ReplyDelete
  4. Thanks for the nice comments! My research into Android graphics was a direct result of the project I was developing at work. Unfortunately I was unexpectedly moved completely off of Android development shortly after I started this blog. :( This explains the lack of follow-up posts. I'd like to make more posts if I ever start working on Android again, but as of right now it doesn't look like that will be happening.

    Lope, I ran into the same problem trying to get photoshop to save a png with no transparent pixels as 32-bit. If none of the pixels has any alpha values, it tries to be clever and saves it as a 24-bit png, even if you specifically tell it that you want a 32-bit file. Very annoying! One way to know for sure whether your png has the correct format is to inspect the file with a hex editor, or any text editor that will give you the ascii value of the byte your cursor is at. If you open up the png image in a hex editor, somewhere near the beginning of the file there should be the four character string "IHDR", this means the data following that string is the image header. The 8 bytes following that string contain the image's width and height, and following those are 2 bytes(at offset 24 and 25) that we are interested in. The first byte is the number of bits per color channel in the image. This can be 1, 2, 4, 8, or 16. For a 32-bit image, we want it to be 8. The next byte is the "color mode" of the image. For a 24-bit image, this value will be 2. For a 32-bit image, it will be 6. So if these two bytes do not contain the values '8' and '6', you do not have a 32-bit png with an alpha channel. As far as how to create an image with an alpha channel, I created the images in Photoshop, saved them as 24-bit pngs, then used some other graphics program to "truly" save them in 32-bit. The program I used at the time escapes me, but there's an open-source image conversion program at http://www.imagemagick.org/ that looks like it will do the trick.

    ReplyDelete
  5. Thanks a lot for your reply, I will look into it. Unfortunately I was moved to different project as well so I am not sure when I will get to it

    ReplyDelete
  6. Today I remembered which program I used to save the images as 32-bit PNGs. It was Microsoft's Paint.NET. You can find it here:

    http://www.getpaint.net/

    ReplyDelete
  7. This article has really helped me fix my banding/dithering problems, thank you so much for your help! I now feel much more confident in my product.

    Regards, Chris

    ReplyDelete
  8. By the way, with Paint.net (my fav paint prog) you can save a much smaller file (8-bit) so long as you hack one of the pixels as alpha (using the erase tool). You can save about 1/2 the file size by doing this and the results are very similar on Android. I know it's not ideal but who's gonna notice a single transparent pixel, huh?

    ReplyDelete
  9. GIMP will let you simply "Add an Alpha Channel"

    Also, been playing with this, at runtime, with promising results:

    bitmapWithAlpha = bitmapNoAlpha.copy(Bitmap.Config.ARGB_8888, true);

    Great article!

    ReplyDelete
  10. Hi, This is a nice post and helped me a lot.I am having a problem in my project.My actual requirement is, To make an image visible(background) through other image(foreground) which is displayed on it.How to do it with android.Could you please help me with this regard.

    ReplyDelete
  11. Greate article, save me a lot of time.

    I still don't apply this on my application, I'm converting a byte[] to a Image manually, and I think the problem of resolution can be solved with this info. Ty!

    ReplyDelete
  12. Great article, once I searched for this info on Bitmaps and had to give up (though later I learned how to cope). Sums it up veeeery nicely, big thanks!

    ReplyDelete
  13. Good article - thanks.

    I postpone dithering on images that I want to alter in code when they come from embedded resources with:

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inDither = false;
    BitmapFactory.decodeResource(resources, resourceId, options);

    but how to achieve the equivalent with images from the gallery? There seems no way to defeat the dithering on load when all you have is a URI to the image in the gallery?

    bitmap = Media.getBitmap(contentResolver, imageUri);

    Pete

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete