Every Image Size in Kb People Upload

Have you noticed when y'all take a picture show on your smartphone and upload/share it to social media like Telegram, Instagram, WhatsApp, etc. Usually earlier sending it to the remote server, the quality of the original prototype is reduced but not so much and still negligible to the naked eye to get a clear picture of the whole image?.

Why and How do they do that? Well, that's what I'll talk most hither.

📓 I previously wrote How To Create Menu Attachment Popup Like WhatsApp on Android on Medium if that also pique your interest.

Results

Wouldn't it exist prissy to come across results first? So, I created a pilot Android application project to implement the Telegram image optimization algorithm and compare the input paradigm (the original) with the output epitome (the scaled ane) resolution and file size. I share the projection GitHub repository link beneath for you lot to effort. So, I continue the internet and download several high-resolution images I could detect. Hither are the optimization results:

telegram image compression 1


telegram image compression 2

The output file size would range from 86 KB to 400 KB. If you lot discover, the highest image resolution is the famous Van Gogh - Starry Night original file with 30000 10 23756 resolution and 205.1 MB file size got optimized to 1280 x 1014 HD Resolution and 389 KB of file size 😱, that is ~99.viii% file size reduction.

💡 I am using 1920x1080: 420dpi emulator resolution

These are optimization result from photos I took with my telephone (Samsung S8):

telegram image compression 3

I don't know almost yous, but in my stance, this is impressive image optimization. Yous can nevertheless see a articulate epitome on each optimization output with a significantly smaller file size than the original. Of grade the optimized image will become blurry if you lot zoom information technology in compared with the original. Just, the trade-offs are worth it for chat applications like Telegram.

Why The Image Quality is Reduced?

Well, there must exist several reasons depends on the company. The obvious ane would be the cost of the storage infinite on the remote server. I estimate the second reason is upload time or load time. The smaller the size of the image, the faster the image will be sent and loaded over a network. So with that, we also got smaller bandwidth usage.

Let'south do the math, shall nosotros? Now, this is merely based on my supposition. Permit'due south assume a large social media company has deject storage somewhere to relieve all media-related files (all images). The total storage capacity it has is 10 TB, which means it'south ten,000 GB or 10,000,000 MB and the price is $0.02 per GB per month (I took from Google Standard Cloud Storage pricing). So the company should pay $200/month or $2,400/year for the cloud storage.

Assuming photos taken from a smartphone have ~3 MB/photo in size and the visitor shop all images without optimizing it, a.chiliad.a the original quality, the storage can agree for almost three.3 Million of photos. What if the images are optimized before uploading them to the deject storage? Assuming the company reduces the original image quality to 1280x720 (720p) HD resolution which Telegram currently implemented as of this mail released and the size is ~600 KB/image, now the storage can hold for about 16.half-dozen Meg of photos. That is 5x of images they previously can store, and they could relieve $ix,600/twelvemonth.

And that is but from the Backend side. From the user's side, it would salvage their internet quota, which may likewise exist translated to their money. Non to mention time-saving considering the image upload/download is faster, reducing the user's take chances of getting a timeout connection.

I don't say that every company must reduce the image quality before sending the paradigm to the server. Notwithstanding, there are merchandise-offs it must consider. It depends on each company's use example and business model. For case, a marketplace for photography images would store the original pictures because people would buy photos with high resolution.

What is Telegram?

I'grand sure nearly of you already familiar with this app. It's a robust chatting application created by Pavel Durov available on desktop, mobile, and web. I think this app is a strong competitor with WhatsApp conversation awarding owned past Facebook. Even so, based on my feel using those apps, Telegram has a slight upper hand considering it has a threescore FPS blithe sticker back up which WhatsApp did not support yet.

Why Telegram?

Every bit a Software Engineer, I as well like Telegram more than considering the source code for the Android app is open-sourced on this GitHub repo and the app has been downloaded for most ~8 meg users on Google Play Store equally of this writing.

What I don't like about it is that even though the source code is open-sourced, at that place is no code organization whatsoever, making it nearly impossible to read/interpret by the brain. For instance, I noticed Telegram do not employ XML file for view layout. Instead, they create all views programmatically. aaaand there is neither unit nor integration tests 😱.

fail testing

Put bated the fact that information technology has extremely low readability. Information technology contains several hidden gems that may pique your interest. Every bit an app that serves over 7 1000000 users on Android, there are several practices worth learning from it. For case, the 1 I volition talk about in this post, how Telegram optimize images before uploading them to the remote server.

How I Navigate Through The Source Code

I am using Android Studio (Every bit) as the IDE for debugging Telegram source lawmaking. In the beginning, It was a bit rough for me to pinpoint whats function gets called when users start to send/upload images. So I beginning past going through the manifest first, looking for the awarding class and the main activity. I then offset placing many breakpoints on every line of the activeness lifecycle 😱.

Apparently, Telegram uses custom fragment add/remove implementation. And then, it is pretty difficult to find what views get rendered or clicked and what role gets called. Afterwards a few days of debugging and most to surrender all of a sudden god slap me in the face up 😵 to finish wasting my time and telling me there is another way, I remember that AS has The Android Profiler feature which can help me profile CPU activity.

With the Sample Java Methods record feature from The Android Profiler I tin can encounter which role gets called when the user interacts with the view. In our case, information technology's when the user tap sends image push.

telegram profiling 2

Once I found the suspicious function triggering the upload/ship image, I created a logger function to help me identify the stack trace and impress it to the Logcat. The logger class I created:

                                                    package                                  org.telegram.logger;                                                                                                          import                                  com.google.android.exoplayer2.util.Log;                                                                                                          public                                                form                                                LoggerUtil                                  {                                                                                                                                          public                                                static                                                final                                                String                                                DEFAULT_KEY                                  =                                "LOG_INSPECT"                ;                                                                                                                                          public                                                static                                                void                                                printCurrentStackTrace                () {                                                                                                    StringBuilder                                                sb                                  =                                new                                                StringBuilder                ();                                                                                                    for                                  (                StackTraceElement                                                ste                                                :                                                Thread                .                currentThread                ().                getStackTrace                ()) {                                                                                                    sb                .                append                (ste);                                                                                                    sb                .                append                (                "                \due north                "                );                                                                                      }                                                                                                    Log                .                d                (DEFAULT_KEY,                                sb                .                toString                ());                                                                                      }                                                                                                                                          public                                                static                                                void                                                printLog                (                String                                                msg                ) {                                                                                                    printLog                (DEFAULT_KEY, msg);                                                                                      }                                                                                                                                          public                                                static                                                void                                                printLog                (                String                                                key                ,                                String                                                msg                ) {                                                                                                    Log                .                d                (primal, msg);                                                                                      }                                                                    }                                            

The Logcat volition impress the stack trace and other log messages I put in the code. In one case I ostend what part gets chosen, I go along debugging line by line using the debugger.

telegram profiling logcat

How Telegram do it?

How do they optimize the images? That'south what we volition explore in this post. Fortunately, equally I went through the paradigm optimization code, in that location is no indication Telegram is using native C/C++ lib code such equally FFmpeg, mozjpeg, libjpeg-turbo or any other JNI call for it. So, I tin can purely rewrite it with Kotlin on the pilot project.

Although, I notice the FFmpeg and mozjpeg are divers as the dependencies, but I don't know when information technology is used.

The Big Moving-picture show

I selectively copy, paste, and rewrite the compression code with Kotlin from Telegram source lawmaking to the airplane pilot project. And so, I try to run the app whether the output image is generating the same dimension and file size compared to when I upload information technology in i of my chat rooms on Telegram.

Long story curt, I am able to generate the aforementioned output every bit Telegram does. I effort to get the idea of what the code does and remove the unnecessary office of the original lawmaking. After I understand the majority of the lawmaking, I refactor it to be more readable. Finally, I end up with a Kotlin object with one public office and several private functions. The public function would look like this:

                                                    object                                  ImageOptimizer {                                                                                                                                          /**                                                                                      * @param context the application environment                                                                                      * @param imageUri the input epitome uri. usually "content://..."                                                                                      * @param compressFormat the output paradigm file format                                                                                      * @param maxWidth the output image max width                                                                                      * @param maxHeight the output image max top                                                                                      * @param useMaxScale determine whether to use the bigger dimension                                                                                      * between [maxWidth] or [maxHeight]                                                                                      * @param quality the output prototype shrink quality                                                                                      * @param minWidth the output image min width                                                                                      * @param minHeight the output image min summit                                                                                      *                                                                                      * @return output image [android.cyberspace.Uri]                                                                                      */                                                                                                    fun                                                optimize                (                                                                                                    context                :                                Context                ,                                                                                                    imageUri                :                                Uri                ,                                                                                                    compressFormat                :                                Bitmap                .                CompressFormat                ,                                                                                                    maxWidth                :                                Bladder                ,                                                                                                    maxHeight                :                                Float                ,                                                                                                    useMaxScale                :                                Boolean                ,                                                                                                    quality                :                                Int                ,                                                                                                    minWidth                :                                Int                ,                                                                                                    minHeight                :                                Int                                                                                      ):                                Uri?                                  {                                                                                                    /**                                                                                      * Decode uri bitmap from action result using content provider                                                                                      */                                                                                                    val                                  bmOptions:                                BitmapFactory                .                Options                                  = decodeBitmapFromUri(context, imageUri)                                                                                                                                          /**                                                                                      * Calculate scale factor of the bitmap relative to [maxWidth] and [maxHeight]                                                                                      */                                                                                                    val                                  scaleDownFactor:                                Float                                  = calculateScaleDownFactor(                                                                                      bmOptions, useMaxScale, maxWidth, maxHeight                                                                                      )                                                                                                                                          /**                                                                                      * Since [BitmapFactory.Options.inSampleSize] merely accept value with power of two,                                                                                      * we calculate the nearest power of ii to the previously calculated scaleDownFactor                                                                                      * bank check md [BitmapFactory.Options.inSampleSize]                                                                                      */                                                                                      setNearestInSampleSize(bmOptions, scaleDownFactor)                                                                                                                                          /**                                                                                      * ii things we do here with epitome matrix:                                                                                      * - Conform image rotation                                                                                      * - Scale image matrix based on remaining [scaleDownFactor / bmOption.inSampleSize]                                                                                      */                                                                                                    val                                  matrix:                                Matrix                                  = calculateImageMatrix(                                                                                      context, imageUri, scaleDownFactor, bmOptions                                                                                      ) ?:                                return                                                null                                                                                                                                          /**                                                                                      * Create new bitmap based on defined bmOptions and calculated matrix                                                                                      */                                                                                                    val                                  newBitmap:                                Bitmap                                  = generateNewBitmap(                                                                                      context, imageUri, bmOptions, matrix                                                                                      ) ?:                                return                                                null                                                                                                    val                                  newBitmapWidth = newBitmap.width                                                                                                    val                                  newBitmapHeight = newBitmap.height                                                                                                                                          /**                                                                                      * Determine whether to scale up the prototype or not if the                                                                                      * image width and height is below minimum dimension                                                                                      */                                                                                                    val                                  shouldScaleUp:                                Boolean                                  = shouldScaleUp(                                                                                      newBitmapWidth, newBitmapHeight, minWidth, minHeight                                                                                      )                                                                                                                                          /**                                                                                      * Summate the final scaleUpFactor if the image need to be scaled up.                                                                                      */                                                                                                    val                                  scaleUpFactor:                                Float                                  = calculateScaleUpFactor(                                                                                      newBitmapWidth.toFloat(), newBitmapHeight.toFloat(), maxWidth, maxHeight,                                                                                      minWidth, minHeight, shouldScaleUp                                                                                      )                                                                                                                                          /**                                                                                      * calculate the final width and peak based on concluding scaleUpFactor                                                                                      */                                                                                                    val                                  finalWidth:                                Int                                  = finalWidth(newBitmapWidth.toFloat(), scaleUpFactor)                                                                                                    val                                  finalHeight:                                Int                                  = finalHeight(newBitmapHeight.toFloat(), scaleUpFactor)                                                                                                                                          /**                                                                                      * Generate the final bitmap, by scaling up if needed                                                                                      */                                                                                                    val                                  finalBitmap:                                Bitmap                                  = scaleUpBitmapIfNeeded(                                                                                      newBitmap, finalWidth, finalHeight, scaleUpFactor, shouldScaleUp                                                                                      )                                                                                                                                          /**                                                                                      * shrink and save image                                                                                      */                                                                                                    val                                  imageFilePath:                                Cord                                  = compressAndSaveImage(                                                                                      finalBitmap, compressFormat, quality                                                                                      ) ?:                                render                                                null                                                                                                                                          render                                                Uri                .fromFile(                File                (imageFilePath))                                                                                      }                                                                                                          }                                            

I purposely declare the variable type for each declaration to give you an idea of what data gets returned from each function call. If y'all check the caller of this long-running function is executed on the background thread using Kotlin Coroutine.

Telegram also uses the algorithm to generate thumbnail of the input image. the just departure is the value passed as the arguments for the maxWidth, maxHeight and quality paremeters.

Decoding The Input Image

                                                    private                                                fun                                                decodeBitmapFromUri                (                                                                                                    context                :                                Context                ,                                                                                                    imageUri                :                                Uri                                                                    ):                                BitmapFactory                .                Options                                  {                                                                                                    val                                  bmOptions =                                BitmapFactory                .                Options                ().                apply                                  {                                                                                      inJustDecodeBounds =                                true                                                                                      }                                                                                                    val                                  input:                                InputStream?                                  = context.contentResolver.openInputStream(imageUri)                                                                                                    BitmapFactory                .decodeStream(input,                                null                , bmOptions)                                                                                      input?.close()                                                                                                    return                                  bmOptions                                                                    }                                            

Cypher special here, we decode the input/original into BitmapFactory.Options. We use input data in the form of content://... uri scheme that nosotros got from the activity issue.

Don't forget to utilize inJustDecodeBounds = true or else the high-resolution input paradigm Bitmap will get allocated into memory, potentially leading to OOM (Out Of Retentivity) exception.

There is a slight difference here from what Telegram does. They use input Uri with file://... uri scheme. Converting the content to file scheme is a footling tricky. I don't know why they convert it, just I'm guessing information technology's for the native C/C++ library that only supports the file://.. protocol (ex: FFmpeg). Considering I only use Kotlin, I figure I don't need to catechumen it to the file scheme.

Summate the Scale Factor

                                                    private                                                fun                                                calculateScaleDownFactor                (                                                                                                    bmOptions                :                                BitmapFactory                .                Options                ,                                                                                                    useMaxScale                :                                Boolean                ,                                                                                                    maxWidth                :                                Float                ,                                                                                                    maxHeight                :                                Float                                                                    ):                                Float                                  {                                                                                                    val                                  photoW = bmOptions.outWidth.toFloat()                                                                                                    val                                  photoH = bmOptions.outHeight.toFloat()                                                                                                    val                                  widthRatio = photoW / maxWidth                                                                                                    val                                  heightRatio = photoH / maxHeight                                                                                                    var                                  scaleFactor =                                if                                  (useMaxScale) {                                                                                      max(widthRatio, heightRatio)                                                                                      }                                else                                  {                                                                                      min(widthRatio, heightRatio)                                                                                      }                                                                                                    if                                  (scaleFactor <                                one                ) {                                                                                      scaleFactor =                                1f                                                                                      }                                                                                                    return                                  scaleFactor                                                                    }                                            

In this part, we summate the scale-down factor of the original prototype with the max width and max height. As for Telegram, they use value of 1280 (Hard disk drive Resolution) for the maxWidth and maxHeight parameters. For the thumbnail size information technology uses value of ninety.

Notation that in this part, the scale factor has Float data type.

Summate Nearest inSampleSize

                                                    private                                                fun                                                setNearestInSampleSize                (                                                                                                    bmOptions                :                                BitmapFactory                .                Options                ,                                                                                                    scaleFactor                :                                Bladder                                                                    ) {                                                                                      bmOptions.inJustDecodeBounds =                                false                                                                                      bmOptions.inSampleSize = scaleFactor.toInt()                                                                                                    if                                  (bmOptions.inSampleSize %                                2                                  !=                                0                ) {                                                                                                    var                                  sample =                                one                                                                                                    while                                  (sample *                                two                                  < bmOptions.inSampleSize) {                                                                                      sample *=                                2                                                                                      }                                                                                      bmOptions.inSampleSize = sample                                                                                      }                                                                    }                                            

This is the first office of the image scaling. We can use the BitmapFactory.Options.inSampleSize to scale downwardly the image. The inSampleSize only accepts integer value with the ability of 2 such every bit 2, 4, eight, 16, ... every bit so it says on its documentation folio. The strange thing happened when I debug an image with inSampleSize value equal to 14. The nearest rounded down powers of ii of 14 would exist 8 but, the image even so got scaled down to 1/14 of the original. Why? I accept no idea. Possibly the underlying logic of information technology has changed, and the developer forgets to update the documentation cord.

I manage to discover several other people on Reddit who see the same affair.

Calculate Paradigm Matrix

                                                    individual                                                fun                                                calculateImageMatrix                (                                                                                                    context                :                                Context                ,                                                                                                    imageUri                :                                Uri                ,                                                                                                    scaleFactor                :                                Float                ,                                                                                                    bmOptions                :                                BitmapFactory                .                Options                                                                    ):                                Matrix?                                  {                                                                                                    val                                  input:                                InputStream                                  = context.contentResolver.openInputStream(imageUri) ?:                                return                                                null                                                                                                    val                                  exif =                                ExifInterface                (input)                                                                                                    val                                  matrix =                                Matrix                ()                                                                                                    val                                  orientation:                                Int                                  = exif.getAttributeInt(                                                                                                    ExifInterface                .                TAG_ORIENTATION                ,                                                                                                    ExifInterface                .                ORIENTATION_NORMAL                                                                                      )                                                                                                    when                                  (orientation) {                                                                                                    ExifInterface                .                ORIENTATION_ROTATE_90                                  -> matrix.postRotate(                                                                                                    90f                                                                                      )                                                                                                    ExifInterface                .                ORIENTATION_ROTATE_180                                  -> matrix.postRotate(                                                                                                    180f                                                                                      )                                                                                                    ExifInterface                .                ORIENTATION_ROTATE_270                                  -> matrix.postRotate(                                                                                                    270f                                                                                      )                                                                                      }                                                                                                    val                                  remainingScaleFactor = scaleFactor / bmOptions.inSampleSize.toFloat()                                                                                                    if                                  (remainingScaleFactor >                                1                ) {                                                                                      matrix.postScale(                1.0f                                  / remainingScaleFactor,                                1.0f                                  / remainingScaleFactor)                                                                                      }                                                                                      input.close()                                                                                                    return                                  matrix                                                                    }                                            

Later computing the inSampleSize value, we and so adjust the image orientation and more image scaling if there is a remaining unscaled portion of the paradigm. What practice you mean? As I said before, inSampleSize but accepts an integer, whereas the scaleFactor is a Bladder. So, there is a possibility the image is not fully scaled downward because the decimal signal is removed. Also, inSampleSize only accept value with powers of 2. Assuming nosotros got a calibration gene of iii, in that location are all the same several pixels of the image portion not scaled down considering information technology is rounded downwardly to 2. Hither comes the matrix!

To determine the orientation of an epitome, we can use the Exif data of the epitome. To support the old API, I recommend using the AndroidX Exifinterface library. Hither, I'm starting to familiarize myself with digital images and their relation to matrices. With matrices, you tin translate, calibration, rotate, and skew an image.

💡 Did you know that some of the predefined ImageView.ScaleType values are Matrix?

After adjusting the image orientation, we continue with image scaling. Beginning thing first, we need to summate how much we demand to scale down from the previous inSampleSize scaled image. Back to my earlier case, how do we calculate the matrix scale factor if we got a scale downwards factor of three and inSampleSize value of ii? I call back it's better for you to see the note I created when I was trying to empathise what information technology's doing

matrix scaling 4

In the film higher up, we got a scale cistron of 3 and the inSampleSize can only hold a value of 2. With inSampleSize we can get i/2 of the prototype, just we withal want to calibration it downward fifty-fifty more because the original scale down factor is 3. To calculate the matrix scale, nosotros need to summate the value of 1 / (calibration factor / inSampleSize). With that, we got the matrix scale value of 0.66, which we then pass it to the Matrix.postScale(sx, sy) function. I'll permit that sink in.

I found several sources that can aid you familiarizes yourself with Matrix on digital images:

  • Agreement Android Matrix transformations
  • Digital images and matrices
  • Conspicuously sympathize matrix adding
  • Matrices and Digital Images
  • Matrices for developers
  • Understanding Matrices to Perform Basic Image Processing on Digital Images

Creating The Scaled Image

                                                    private                                                fun                                                generateNewBitmap                (                                                                                                    context                :                                Context                ,                                                                                                    imageUri                :                                Uri                ,                                                                                                    bmOptions                :                                BitmapFactory                .                Options                ,                                                                                                    matrix                :                                Matrix                                                                    ):                                Bitmap?                                  {                                                                                                    var                                  bitmap:                                Bitmap?                                  =                                null                                                                                                    val                                  inputStream:                                InputStream?                                  = context.contentResolver.openInputStream(imageUri)                                                                                                    endeavour                                  {                                                                                      bitmap =                                BitmapFactory                .decodeStream(inputStream,                                zero                , bmOptions)                                                                                                    if                                  (bitmap !=                                null                ) {                                                                                                    val                                  matrixScaledBitmap:                                Bitmap                                  =                                Bitmap                .createBitmap(                                                                                      bitmap,                                0                ,                                0                , bitmap.width, bitmap.height, matrix,                                true                                                                                      )                                                                                                    if                                  (matrixScaledBitmap != bitmap) {                                                                                      bitmap.recycle()                                                                                      bitmap = matrixScaledBitmap                                                                                      }                                                                                      }                                                                                      input                Stream?                .close()                                                                                      }                                take hold of                                  (                e                :                                Throwable                ) {                                                                                      e.printStackTrace()                                                                                      }                                                                                                    render                                  bitmap                                                                    }                                            

After computing the scale factor for inSampleSize and the matrix, we continue creating the bitmap. Simply, first, information technology volition create and allocate bitmap to retentivity from the divers BitmapFactory.Options.

Then, rotate or scale it down even more with a matrix if needed. If the matrixScaledBitmap bitmap variable is not the same every bit the previously assigned bitmap variable (because matrix scale factor is non ane or needs to be reoriented), we then recycle the first bitmap consignment to remove its resource allotment from retention and reassign it with matrixScaledBitmap. Lastly, return the bitmap variable.

Check If The Epitome Demand To be Scaled Upwardly

                                                    private                                                fun                                                shouldScaleUp                (                                                                                                    photoW                :                                Int                ,                                                                                                    photoH                :                                Int                ,                                                                                                    minWidth                :                                Int                ,                                                                                                    minHeight                :                                Int                                                                    ):                                Boolean                                  {                                                                                                    render                                  (minWidth !=                                0                                  && minHeight !=                                0                                  && (photoW < minWidth || photoH < minHeight))                                                                    }                                            

At this point, Nosotros check whether we should scale up the previously generated bitmap if the dimension of information technology is below the minimum.

Calculate The Scale Up Factor

                                                    private                                                fun                                                calculateScaleUpFactor                (                                                                                                    photoW                :                                Float                ,                                                                                                    photoH                :                                Bladder                ,                                                                                                    maxWidth                :                                Float                ,                                                                                                    maxHeight                :                                Float                ,                                                                                                    minWidth                :                                Int                ,                                                                                                    minHeight                :                                Int                ,                                                                                                    shouldScaleUp                :                                Boolean                                                                    ):                                Float                                  {                                                                                                    var                                  scaleUpFactor:                                Float                                  = max(photoW / maxWidth, photoH / maxHeight)                                                                                                    if                                  (shouldScaleUp) {                                                                                      scaleUpFactor =                                if                                  (photoW < minWidth && photoH > minHeight) {                                                                                      photoW / minWidth                                                                                      }                                else                                                if                                  (photoW > minWidth && photoH < minHeight) {                                                                                      photoH / minHeight                                                                                      }                                else                                  {                                                                                      max(photoW / minWidth, photoH / minHeight)                                                                                      }                                                                                      }                                                                                                    render                                  scaleUpFactor                                                                    }                                            

If the image does not demand scaling up, the scaleUpFactor will remain i. If yes, the value will be beneath 1.

Calculate the Final Width and Height

                                                    private                                                fun                                                finalWidth                (                                                                                                    photoW                :                                Float                ,                                scaleUpFactor                :                                Float                                                                    ):                                Int                                  {                                                                                                    render                                  (photoW / scaleUpFactor).toInt()                                                                    }                                                                                                          private                                                fun                                                finalHeight                (                                                                                                    photoH                :                                Float                ,                                scaleUpFactor                :                                Float                                                                    ):                                Int                                  {                                                                                                    return                                  (photoH / scaleUpFactor).toInt()                                                                    }                                            

As the function name implies, nosotros calculate the final width and peak of the prototype output. Same as earlier, the width and pinnacle will remain the same, a.grand.a not scaled up if the scaleUpFactor is 1.

Generate the Last Bitmap

                                                    private                                                fun                                                scaleUpBitmapIfNeeded                (                                                                                                    bitmap                :                                Bitmap                ,                                                                                                    finalWidth                :                                Int                ,                                                                                                    finalHeight                :                                Int                ,                                                                                                    scaleUpFactor                :                                Float                ,                                                                                                    shouldScaleUp                :                                Boolean                                                                    ):                                Bitmap                                  {                                                                                                    val                                  scaledBitmap:                                Bitmap                                  =                                if                                  (scaleUpFactor >                                1                                  || shouldScaleUp) {                                                                                                    Bitmap                .createScaledBitmap(bitmap, finalWidth, finalHeight,                                truthful                )                                                                                      }                                else                                  {                                                                                      bitmap                                                                                      }                                                                                                    if                                  (scaledBitmap != bitmap) {                                                                                      bitmap.recycle()                                                                                      }                                                                                                    return                                  scaledBitmap                                                                    }                                            

In this function, we generate the final bitmap. The new bitmap will be allocated to memory if we demand to scale it up. If a new bitmap is generated, nosotros also need to recycle the previous bitmap considering nosotros no longer demand it.

Compress & Save the Bitmap

                                                    private                                                fun                                                compressAndSaveImage                (                                                                                                    bitmap                :                                Bitmap                ,                                                                                                    compressFormat                :                                Bitmap.CompressFormat?                ,                                                                                                    quality                :                                Int                ,                                                                    ):                                String?                                  {                                                                                                    val                                  uniqueID =                                UUID                .randomUUID().toString()                                                                                                    val                                  fileName =                                "test_optimization_$uniqueID.jpg"                                                                                                    val                                  fileDir =                                File                (                "/storage/emulated/0/Download/"                )                                                                                                    val                                  imageFile =                                File                (fileDir, fileName)                                                                                                    val                                  stream =                                FileOutputStream                (imageFile)                                                                                      bitmap.shrink(compressFormat, quality, stream)                                                                                      stream.shut()                                                                                      bitmap.recycle()                                                                                                    return                                  imageFile.absolutePath                                                                    }                                            

Finally, in the last function, nosotros salve & shrink the generated bitmap. Telegram utilize compress quality of fourscore and Bitmap.CompressFormat.JPEG equally the paradigm format. For thumbnail, it uses quality value of 55.

For the sake of the tutorial, I don't relieve the epitome in a svelte manner. You might want to use content resolver to salvage media-related files. You lot tin can also relieve it to app-specific storage if you don't want other apps to discover the compressed images.

Notation, that this algorithm will create a new prototype file on your Download folder for each optimization. Therefore, yous might want to delete it subsequently, or you lot'll stop up with a bunch of information technology.

Try It Yourself

For the full lawmaking file of the ImageOptimizer object, yous can check it here. Y'all can also cheque and run the code yourself in this Github repository.

I hope this mail service volition give you lot some insight on how to bargain with image scaling or optimization on Android development. Cheers.

wallinmordice.blogspot.com

Source: https://rifqimfahmi.dev/blog/telegram-like-image-optimization-on-android/

0 Response to "Every Image Size in Kb People Upload"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel