]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveWebpageImageCoroutine.kt
d6c984625ccf3233a0d00eea0b6d6820ca7bee7c
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / coroutines / SaveWebpageImageCoroutine.kt
1 /*
2  * Copyright0 2019-2024 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser Android is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.coroutines
21
22 import android.app.Activity
23 import android.graphics.Bitmap
24 import android.graphics.Canvas
25 import android.net.Uri
26 import android.provider.OpenableColumns
27
28 import com.google.android.material.snackbar.Snackbar
29
30 import com.stoutner.privacybrowser.R
31 import com.stoutner.privacybrowser.views.NestedScrollWebView
32
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.Dispatchers
35 import kotlinx.coroutines.launch
36 import kotlinx.coroutines.withContext
37
38 import java.io.ByteArrayOutputStream
39
40 class SaveWebpageImageCoroutine {
41     fun save(activity: Activity, fileUri: Uri, nestedScrollWebView: NestedScrollWebView) {
42         // Use a coroutine to save the webpage image.
43         CoroutineScope(Dispatchers.Main).launch {
44             // Get a cursor from the content resolver.
45             val contentResolverCursor = activity.contentResolver.query(fileUri, null, null, null)
46
47             // Move to the first row.
48             contentResolverCursor!!.moveToFirst()
49
50             // Get the file name from the cursor.
51             val fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
52
53             // Close the cursor.
54             contentResolverCursor.close()
55
56             // Create a saving image snackbar.
57             val savingImageSnackbar = Snackbar.make(nestedScrollWebView, activity.getString(R.string.processing_image, fileNameString), Snackbar.LENGTH_INDEFINITE)
58
59             // Display the saving image snackbar.
60             savingImageSnackbar.show()
61
62             // Create a webpage bitmap.  Once the Minimum API >= 33 Bitmap.Config.RGBA_1010102 can be used instead of ARGB_8888.
63             // RGBA_F16 can't be used because it produces black output for the part of page not currently visible on the screen.
64             val webpageBitmap = Bitmap.createBitmap(nestedScrollWebView.getHorizontalScrollRange(), nestedScrollWebView.getVerticalScrollRange(), Bitmap.Config.ARGB_8888)
65
66             // Create a canvas.
67             val webpageCanvas = Canvas(webpageBitmap)
68
69             // Draw the current webpage onto the bitmap.  The nested scroll WebView commands must be run on the UI thread.
70             nestedScrollWebView.draw(webpageCanvas)
71
72             // Compress the image on the IO thread.
73             withContext(Dispatchers.IO) {
74                 // Create a webpage PNG byte array output stream.
75                 val webpageByteArrayOutputStream = ByteArrayOutputStream()
76
77                 // Convert the bitmap to a PNG.  `0` is for lossless compression (the only option for a PNG).  This compression takes a long time.
78                 // Once the minimum API >= 30 this could be replaced with WEBP_LOSSLESS.
79                 webpageBitmap.compress(Bitmap.CompressFormat.PNG, 0, webpageByteArrayOutputStream)
80
81                 try {
82                     // Create an image file output stream.
83                     val imageFileOutputStream = activity.contentResolver.openOutputStream(fileUri)!!
84
85                     // Write the webpage image to the image file.
86                     webpageByteArrayOutputStream.writeTo(imageFileOutputStream)
87
88                     // Close the output stream.
89                     imageFileOutputStream.close()
90
91                     // Update the UI.
92                     withContext(Dispatchers.Main) {
93                         // Dismiss the saving image snackbar.
94                         savingImageSnackbar.dismiss()
95
96                         // Display the image saved snackbar.
97                         Snackbar.make(nestedScrollWebView, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
98                     }
99                 } catch (exception: Exception) {
100                     // Update the UI.
101                     withContext(Dispatchers.Main) {
102                         // Dismiss the saving image snackbar.
103                         savingImageSnackbar.dismiss()
104
105                         // Display the file saving error.
106                         Snackbar.make(nestedScrollWebView, activity.getString(R.string.error_saving_file, fileNameString, exception), Snackbar.LENGTH_INDEFINITE).show()
107                     }
108                 }
109             }
110         }
111     }
112 }