Danh Nguyen
Danh Nguyen

Reputation: 125

[Android]-Resize image to upload to server

My sever limit image size to upload image (2MB). I want to upload image from android device to server. I want to resize image. What's the best way I can do resize image?

Upvotes: 6

Views: 15170

Answers (3)

Falchio
Falchio

Reputation: 660

Fix image resolution, file size.

private const val TAG = "Compressor"
private const val START_QUALITY = 100
private const val QUALITY_DECREASE_STEP = 10
private const val DEFAULT_SIZE_LIMIT = 512_000
private const val MARGIN = 30

//image resolution 1024*768
private const val LONG_SIZE = 1024
private const val SHORT_SIZE = 768

object Compressor {
    private val FORMAT = Bitmap.CompressFormat.JPEG


    fun compressImage(
        imageFile: File,
        limitSize: Int = DEFAULT_SIZE_LIMIT,
        watermark: String? = null
    ): String {
        val originalBitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
        val bitmapWithWaterMark = mark(originalBitmap, watermark)

        val (newWidth, newHeight) = calculateNewWidthAndHeight(bitmapWithWaterMark)
        val resizedBitmap = getResizedBitmap(bitmapWithWaterMark, newWidth, newHeight)

        val stream = ByteArrayOutputStream()
        var quality = START_QUALITY
        //compressed Bitmap write to ByteArrayOutputStream
        resizedBitmap.compress(FORMAT, quality, stream)
        while (stream.size() > limitSize && quality > QUALITY_DECREASE_STEP) {
            stream.reset()
            quality -= QUALITY_DECREASE_STEP
            resizedBitmap.compress(FORMAT, quality, stream)
        }
        val base64String = Base64.encodeToString(stream.toByteArray(), Base64.DEFAULT)
        return base64String
    }

    private fun calculateNewWidthAndHeight(waterMarkBitmap: Bitmap): Pair<Int, Int> {
        val newWidth = if (isPortrait(waterMarkBitmap)) SHORT_SIZE else LONG_SIZE
        val newHeight = if (isPortrait(waterMarkBitmap)) LONG_SIZE else SHORT_SIZE
        return Pair(newWidth, newHeight)
    }

    private fun isPortrait(bitmap: Bitmap) = bitmap.height > bitmap.width

    private fun mark(source: Bitmap, watermark: String? = null): Bitmap {
        if (watermark == null) return source
        val width = source.width
        val height = source.height
        val result = Bitmap.createBitmap(width, height, source.config)
        val canvas = Canvas(result)
        canvas.drawBitmap(source, 0f, 0f, null)

        val paint = getPaint(18)
        val (widthPaint, heightPaint) = paint.getTextWidthAndHeight(watermark)

        //watermark background
        val backgroundPaint = getBackgroundPaint()
        canvas.drawRect(
            width - widthPaint - MARGIN,
            height - heightPaint * 2,
            width.toFloat(),
            height.toFloat() - heightPaint + MARGIN,
            backgroundPaint
        )

        //watermark text
        canvas.drawText(
            watermark,
            width - widthPaint,
            height - heightPaint,
            paint
        )

        return result
    }

    private fun getBackgroundPaint(): Paint {
        return Paint().apply {
            style = Paint.Style.FILL
            color = Color.BLACK
        }
    }

    private fun dpToPx(dp: Int): Float {
        return TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            dp.toFloat(),
            App.resources.displayMetrics
        )
    }


    private fun getPaint(textSize: Int, isShadowEnable: Boolean = false): Paint {
        return Paint(Paint.ANTI_ALIAS_FLAG).apply {
            setTextSize(dpToPx(textSize))
            typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)

            if (isShadowEnable) {
                setShadowLayer(2f, 2f, 2f, Color.BLACK)
            }

            color = Color.WHITE
            textAlign = Paint.Align.LEFT
        }
    }

    private fun Paint.getTextWidthAndHeight(text: String): Pair<Float, Float> {
        val baseline = -this.ascent() // ascent() is negative
        val width: Float = this.measureText(text) + dpToPx(8)
        val height: Float = baseline + this.descent() + dpToPx(4)
        return Pair(width, height)
    }


    private fun getResizedBitmap(bitmap: Bitmap, newWidth: Int, newHeight: Int): Bitmap {
        val width = bitmap.width
        val height = bitmap.height
        val scaleWidth = newWidth.toFloat() / width
        val scaleHeight = newHeight.toFloat() / height
        // CREATE A MATRIX FOR THE MANIPULATION
        val matrix = Matrix()
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight)

        // "RECREATE" THE NEW BITMAP
        val resizedBitmap = Bitmap.createBitmap(
            bitmap, 0, 0, width, height, matrix, false
        )
        bitmap.recycle()
        return resizedBitmap
    }
}

Upvotes: 0

Huzaifa Yunus
Huzaifa Yunus

Reputation: 1

You can also do this after encoding your image to string then squeeze the bytes our of it then calculate the length you want before saving the image. in this case, if it's big the user will be forced to change the image. I assumed you have already encoded your image.

     long myBitmapLength = myEncodedImage.getBytes().length/1024;
       if(myBitmapLength < 200){
           //allow saving 
     }

Upvotes: 0

Eren
Eren

Reputation: 2663

Use the code below. With "MAX_IMAGE_SIZE" specify your max file size in kilobayts. In this code; first i resize the image, then i compress it. See comments in the code to understand the logic better.

     public static String resizeAndCompressImageBeforeSend(Context context,String filePath,String fileName){
     final int MAX_IMAGE_SIZE = 700 * 1024; // max final file size in kilobytes

     // First decode with inJustDecodeBounds=true to check dimensions of image
     final BitmapFactory.Options options = new BitmapFactory.Options();
     options.inJustDecodeBounds = true;
     BitmapFactory.decodeFile(filePath,options);

     // Calculate inSampleSize(First we are going to resize the image to 800x800 image, in order to not have a big but very low quality image.
     //resizing the image will already reduce the file size, but after resizing we will check the file size and start to compress image
     options.inSampleSize = calculateInSampleSize(options, 800, 800);

     // Decode bitmap with inSampleSize set
     options.inJustDecodeBounds = false;
     options.inPreferredConfig= Bitmap.Config.ARGB_8888;

     Bitmap bmpPic = BitmapFactory.decodeFile(filePath,options);


     int compressQuality = 100; // quality decreasing by 5 every loop.
     int streamLength;
     do{
         ByteArrayOutputStream bmpStream = new ByteArrayOutputStream();
         Log.d("compressBitmap", "Quality: " + compressQuality);
         bmpPic.compress(Bitmap.CompressFormat.JPEG, compressQuality, bmpStream);
         byte[] bmpPicByteArray = bmpStream.toByteArray();
         streamLength = bmpPicByteArray.length;
         compressQuality -= 5;
         Log.d("compressBitmap", "Size: " + streamLength/1024+" kb");
     }while (streamLength >= MAX_IMAGE_SIZE);

     try {
         //save the resized and compressed file to disk cache
         Log.d("compressBitmap","cacheDir: "+context.getCacheDir());
         FileOutputStream bmpFile = new FileOutputStream(context.getCacheDir()+fileName);
         bmpPic.compress(Bitmap.CompressFormat.JPEG, compressQuality, bmpFile);
         bmpFile.flush();
         bmpFile.close();
     } catch (Exception e) {
         Log.e("compressBitmap", "Error on saving file");
     }
     //return the path of resized and compressed file
     return  context.getCacheDir()+fileName;
 }



 public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
     String debugTag = "MemoryInformation";
     // Image nin islenmeden onceki genislik ve yuksekligi
     final int height = options.outHeight;
     final int width = options.outWidth;
     Log.d(debugTag,"image height: "+height+ "---image width: "+ width);
     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.d(debugTag,"inSampleSize: "+inSampleSize);
     return inSampleSize;
 }

Upvotes: 14

Related Questions