Search code examples
androidpicasso

How to preserve aspect ratio of downloaded image in ImageView and prevent image going beyond boundaries of ImageView


I have an imageView.


        <ImageView
            android:id="@+id/backgroud"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scaleType="centerInside" 
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

I need to download the image from the internet and the size of received image is more then the size of ImageView generally. I am Picasso library. But It always does not respect aspect ratio of image or boundaries of ImageView. I am using Picasso to download the image with Target Object. Seems that the problem is related to Picasso itself, because if i set the image from xml it respects the aspect ratio of image.

getPicassoInstance().load(url)
                        .into(binding.backgroud)
                        .into(backgroud, object :
                            Callback {
                            override fun onError(e: Exception?) {
                                Timber.w("Could not load the background image$e")
                            }

                            override fun onSuccess() {
      

                                        }
                         
                        })

I've tried other Picasso approaches like using 'centerInside' but they are not working as well. Generally it stretches the image to whole width of ImageView without preserving aspect ratio of downloaded image. The width of downloaded image more then the size of phone in portrait mode.


Solution

  • This is the code used in my app:

       /**
         * show the chosen image identified by uri.
         *
         *
         * A Uniform Resource Identifier (URI) is a compact sequence of characters that identifies an abstract or physical resource
         *
         * @param context Context
         * @param uri uri of the image
         * @param myImage ImageView where the image will be displayed
         * @see scaleImage
         */
        @JvmStatic
        fun showImage(context: Context, uri: Uri?, myImage: ImageView) {
            var bitmap: Bitmap? = null
            //
            try {
                val source = ImageDecoder.createSource(
                    context.contentResolver,
                    uri!!
                )
                bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
                    decoder.setTargetSampleSize(1) // shrinking by
                    decoder.isMutableRequired = true // this resolve the hardware type of bitmap problem
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
            //
            // On devices running SDK < 24 (Android 7.0), this method will fail to apply
            // correct density scaling to images loaded from content and file schemes.
            // Applications running on devices with SDK >= 24 MUST specify the
            // targetSdkVersion in their manifest as 24 or above for density scaling
            // to be applied to images loaded from these schemes.
            myImage.setImageBitmap(bitmap)
            scaleImage(context, bitmap, myImage)
        }
    /**
     * scale the image represented in the bitmap.
     * REFER to [stackoverflow](https://stackoverflow.com/questions/8232608/fit-image-into-imageview-keep-aspect-ratio-and-then-resize-imageview-to-image-d)
     * answer of [Jarno Argillander](https://stackoverflow.com/users/1030049/jarno-argillander)
     *
     * @param context Context
     * @param bitmap bitmap of the image
     * @param view imageview where the image will be displayed
     * @see dpToPx
     */
    @Throws(NoSuchElementException::class)
    @JvmStatic
    private fun scaleImage(context: Context, bitmap: Bitmap?, view: ImageView) {
        // Get current dimensions AND the desired bounding box
        var width: Int
        width = try {
            bitmap!!.width
        } catch (e: NullPointerException) {
            throw NoSuchElementException("Can't find bitmap on given view/drawable")
        }
        var height = bitmap.height
        val bounding = dpToPx(context,150)
        // Log.i("Test", "original width = " + Integer.toString(width));
        // Log.i("Test", "original height = " + Integer.toString(height));
        // Log.i("Test", "bounding = " + Integer.toString(bounding));
        // Determine how much to scale: the dimension requiring less scaling is
        // closer to the its side. This way the image always stays inside your
        // bounding box AND either x/y axis touches it.
        val xScale = bounding.toFloat() / width
        val yScale = bounding.toFloat() / height
        val scale = if (xScale <= yScale) xScale else yScale
        // Log.i("Test", "xScale = " + Float.toString(xScale));
        // Log.i("Test", "yScale = " + Float.toString(yScale));
        // Log.i("Test", "scale = " + Float.toString(scale));
        // Create a matrix for the scaling and add the scaling data
        val matrix = Matrix()
        matrix.postScale(scale, scale)
        // Create a new bitmap and convert it to a format understood by the ImageView
        val scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true)
        width = scaledBitmap.width // re-use
        height = scaledBitmap.height // re-use
        val result = BitmapDrawable(context.resources, scaledBitmap)
        // Log.i("Test", "scaled width = " + Integer.toString(width));
        // Log.i("Test", "scaled height = " + Integer.toString(height));
        // Apply the scaled bitmap
        view.setImageDrawable(result)
        // Now change ImageView's dimensions to match the scaled image
        val params = view.layoutParams as ConstraintLayout.LayoutParams
        params.width = width
        params.height = height
        view.layoutParams = params
        // Log.i("Test", "done");
    }
    
    /**
     * calculate the dimensions of desired bounding box.
     *
     *
     * @param dp int with dimensions of desired bounding box
     * @return int with dimensions of desired bounding box in px
     */
    @JvmStatic
    private fun dpToPx(context: Context, dp: Int): Int {
        val density = context.getApplicationContext().resources.displayMetrics.density
        return Math.round(dp.toFloat() * density)
    }