长图功能
This commit is contained in:
parent
f90b57ace5
commit
7208fa3d8a
@ -106,10 +106,16 @@ class PdfPickerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PdfPickerSource.IMAGES_TO_PDF -> {}
|
PdfPickerSource.IMAGES_TO_PDF -> {}
|
||||||
PdfPickerSource.TO_LONG_IMAGE -> {}
|
PdfPickerSource.TO_LONG_IMAGE -> {
|
||||||
|
val intent = PdfToImageActivity.createIntent(this, pdf.filePath,
|
||||||
|
PdfPickerSource.TO_LONG_IMAGE)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
PdfPickerSource.NONE -> {}
|
PdfPickerSource.NONE -> {}
|
||||||
PdfPickerSource.PDF_TO_IMAGES -> {
|
PdfPickerSource.PDF_TO_IMAGES -> {
|
||||||
val intent = PdfToImageActivity.createIntent(this, pdf.filePath)
|
val intent = PdfToImageActivity.createIntent(this, pdf.filePath,
|
||||||
|
PdfPickerSource.PDF_TO_IMAGES)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -112,6 +112,7 @@ class PdfResultActivity : BaseActivity() {
|
|||||||
private var exitDialog: PromptDialogFragment? = null
|
private var exitDialog: PromptDialogFragment? = null
|
||||||
private val pdfRepository = getRepository()
|
private val pdfRepository = getRepository()
|
||||||
private lateinit var pdfScanner: PdfScanner
|
private lateinit var pdfScanner: PdfScanner
|
||||||
|
private var openedExternal = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -262,7 +263,8 @@ class PdfResultActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
val unlockPassword = intent.getStringExtra(EXTRA_LOCK_UNLOCK_PASSWORD) ?: ""
|
val unlockPassword = intent.getStringExtra(EXTRA_LOCK_UNLOCK_PASSWORD) ?: ""
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
binding.congratulationsDesc.text = getString(R.string.remove_password_successfully)
|
binding.congratulationsDesc.text =
|
||||||
|
getString(R.string.remove_password_successfully)
|
||||||
}
|
}
|
||||||
PdfSecurityUtils.removePasswordFromPdfWithProgress(
|
PdfSecurityUtils.removePasswordFromPdfWithProgress(
|
||||||
filepath, unlockPassword
|
filepath, unlockPassword
|
||||||
@ -402,15 +404,15 @@ class PdfResultActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.okBtn.setOnClickListener {
|
binding.okBtn.setOnClickListener {
|
||||||
if (source == PdfPickerSource.PDF_TO_IMAGES) {
|
if (source == PdfPickerSource.PDF_TO_IMAGES || source == PdfPickerSource.TO_LONG_IMAGE) {
|
||||||
val selectedItem = adapter.getSelectedItem()
|
val selectedItem = adapter.getSelectedItem()
|
||||||
selectedItem?.let {
|
selectedItem?.let {
|
||||||
val string = AppUtils.openImageFile(this, File(selectedItem.filePath))
|
val string = AppUtils.openImageFile(this, File(selectedItem.filePath))
|
||||||
if (string.isNotEmpty()) {
|
if (string.isNotEmpty()) {
|
||||||
showToast(string)
|
showToast(string)
|
||||||
}
|
}
|
||||||
|
openedExternal = true
|
||||||
}
|
}
|
||||||
finish()
|
|
||||||
} else {
|
} else {
|
||||||
val selectedItem = adapter.getSelectedItem()
|
val selectedItem = adapter.getSelectedItem()
|
||||||
selectedItem?.let {
|
selectedItem?.let {
|
||||||
@ -422,6 +424,13 @@ class PdfResultActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (openedExternal) {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
isProcessing = false
|
isProcessing = false
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.all.pdfreader.pro.app.ui.act
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@ -10,31 +11,32 @@ import com.all.pdfreader.pro.app.R
|
|||||||
import com.all.pdfreader.pro.app.databinding.ActivityPdfToImgBinding
|
import com.all.pdfreader.pro.app.databinding.ActivityPdfToImgBinding
|
||||||
import com.all.pdfreader.pro.app.model.PdfPageItem
|
import com.all.pdfreader.pro.app.model.PdfPageItem
|
||||||
import com.all.pdfreader.pro.app.model.PdfPickerSource
|
import com.all.pdfreader.pro.app.model.PdfPickerSource
|
||||||
import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem
|
|
||||||
import com.all.pdfreader.pro.app.ui.adapter.SplitPdfAdapter
|
import com.all.pdfreader.pro.app.ui.adapter.SplitPdfAdapter
|
||||||
import com.all.pdfreader.pro.app.ui.dialog.PdfPasswordProtectionDialogFragment
|
import com.all.pdfreader.pro.app.ui.dialog.PdfPasswordProtectionDialogFragment
|
||||||
import com.all.pdfreader.pro.app.util.AppUtils.setOnSingleClickListener
|
import com.all.pdfreader.pro.app.util.AppUtils.setOnSingleClickListener
|
||||||
import com.all.pdfreader.pro.app.util.FileUtils.isPdfEncrypted
|
import com.all.pdfreader.pro.app.util.FileUtils.isPdfEncrypted
|
||||||
import com.all.pdfreader.pro.app.util.FileUtils.toUnderscoreDateTime
|
|
||||||
import com.all.pdfreader.pro.app.util.PdfUtils
|
import com.all.pdfreader.pro.app.util.PdfUtils
|
||||||
import com.gyf.immersionbar.ImmersionBar
|
import com.gyf.immersionbar.ImmersionBar
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
class PdfToImageActivity : BaseActivity() {
|
class PdfToImageActivity : BaseActivity() {
|
||||||
override val TAG: String = "PdfToImageActivity"
|
override val TAG: String = "PdfToImageActivity"
|
||||||
|
|
||||||
|
|
||||||
var currentPassword: String? = null//只会选择一个文件。直接进行密码传递
|
var currentPassword: String? = null//只会选择一个文件。直接进行密码传递
|
||||||
|
private lateinit var source: PdfPickerSource
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_PDF_PATH = "extra_pdf_path"
|
private const val EXTRA_PDF_PATH = "extra_pdf_path"
|
||||||
|
private const val EXTRA_SOURCE = "extra_source"
|
||||||
fun createIntent(context: Context, filePath: String): Intent {
|
fun createIntent(context: Context, filePath: String, source: PdfPickerSource): Intent {
|
||||||
return Intent(context, PdfToImageActivity::class.java).apply {
|
return Intent(context, PdfToImageActivity::class.java).apply {
|
||||||
putExtra(EXTRA_PDF_PATH, filePath)
|
putExtra(EXTRA_PDF_PATH, filePath)
|
||||||
|
putExtra(EXTRA_SOURCE, source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,6 +54,7 @@ class PdfToImageActivity : BaseActivity() {
|
|||||||
.navigationBarColor(R.color.bg_color).init()
|
.navigationBarColor(R.color.bg_color).init()
|
||||||
filePath = intent.getStringExtra(EXTRA_PDF_PATH)
|
filePath = intent.getStringExtra(EXTRA_PDF_PATH)
|
||||||
?: throw IllegalArgumentException("PDF file hash is required")
|
?: throw IllegalArgumentException("PDF file hash is required")
|
||||||
|
source = getSerializableOrDefault(EXTRA_SOURCE, PdfPickerSource.NONE)
|
||||||
if (filePath.isEmpty()) {
|
if (filePath.isEmpty()) {
|
||||||
showToast(getString(R.string.file_not))
|
showToast(getString(R.string.file_not))
|
||||||
finish()
|
finish()
|
||||||
@ -92,13 +95,23 @@ class PdfToImageActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
binding.continueNowBtn.setOnSingleClickListener {
|
binding.continueNowBtn.setOnSingleClickListener {
|
||||||
val selectedPages = pdfPageList.filter { it.isSelected }.map { it.copy() }
|
val selectedPages = pdfPageList.filter { it.isSelected }.map { it.copy() }
|
||||||
val intent = PdfResultActivity.createIntentPdfToImageActivityToResult(
|
val intent = if (source == PdfPickerSource.PDF_TO_IMAGES) {
|
||||||
|
PdfResultActivity.createIntentPdfToImageActivityToResult(
|
||||||
context = this,
|
context = this,
|
||||||
filepath = filePath,
|
filepath = filePath,
|
||||||
list = ArrayList(selectedPages),
|
list = ArrayList(selectedPages),
|
||||||
source = PdfPickerSource.PDF_TO_IMAGES,
|
source = PdfPickerSource.PDF_TO_IMAGES,
|
||||||
password = currentPassword
|
password = currentPassword
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
PdfResultActivity.createIntentPdfToImageActivityToResult(
|
||||||
|
context = this,
|
||||||
|
filepath = filePath,
|
||||||
|
list = ArrayList(selectedPages),
|
||||||
|
source = PdfPickerSource.TO_LONG_IMAGE,
|
||||||
|
password = currentPassword
|
||||||
|
)
|
||||||
|
}
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
@ -172,4 +185,16 @@ class PdfToImageActivity : BaseActivity() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T : Serializable> getSerializableOrDefault(
|
||||||
|
key: String, default: T
|
||||||
|
): T {
|
||||||
|
val result: T? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
intent.getSerializableExtra(key, T::class.java)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION") intent.getSerializableExtra(key) as? T
|
||||||
|
}
|
||||||
|
return result ?: default
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -72,6 +72,10 @@ class ToolsFrag : BaseFrag() {
|
|||||||
val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.PDF_TO_IMAGES)
|
val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.PDF_TO_IMAGES)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
binding.pdfToLongImgBtn.setOnClickListener {
|
||||||
|
val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.TO_LONG_IMAGE)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openImagePicker() {
|
private fun openImagePicker() {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.all.pdfreader.pro.app.util
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Canvas
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.graphics.createBitmap
|
import androidx.core.graphics.createBitmap
|
||||||
@ -24,6 +25,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
import androidx.core.graphics.scale
|
||||||
|
|
||||||
object PdfUtils {
|
object PdfUtils {
|
||||||
|
|
||||||
@ -419,4 +421,91 @@ object PdfUtils {
|
|||||||
fd.close()
|
fd.close()
|
||||||
resultList
|
resultList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出选中的 PDF 页到长图文件
|
||||||
|
*/
|
||||||
|
suspend fun exportPagesAsLongImage(
|
||||||
|
context: Context,
|
||||||
|
inputFile: File,
|
||||||
|
selectedPages: List<PdfPageItem>,
|
||||||
|
outputDir: File,
|
||||||
|
password: String? = null,
|
||||||
|
format: String = "JPG",
|
||||||
|
quality: Int = 100,
|
||||||
|
onProgress: ((current: Int, total: Int) -> Unit)? = null
|
||||||
|
): File? = withContext(Dispatchers.IO) {
|
||||||
|
|
||||||
|
if (selectedPages.isEmpty()) return@withContext null
|
||||||
|
if (!outputDir.exists()) outputDir.mkdirs()
|
||||||
|
|
||||||
|
val pdfium = PdfiumCore(context)
|
||||||
|
val fd = ParcelFileDescriptor.open(inputFile, ParcelFileDescriptor.MODE_READ_ONLY)
|
||||||
|
val doc = if (password.isNullOrEmpty()) pdfium.newDocument(fd) else pdfium.newDocument(fd, password)
|
||||||
|
|
||||||
|
val sorted = selectedPages.sortedBy { it.pageIndex }
|
||||||
|
val bitmaps = mutableListOf<Bitmap>()
|
||||||
|
|
||||||
|
sorted.forEachIndexed { idx, item ->
|
||||||
|
pdfium.openPage(doc, item.pageIndex)
|
||||||
|
val w = pdfium.getPageWidthPoint(doc, item.pageIndex)
|
||||||
|
val h = pdfium.getPageHeightPoint(doc, item.pageIndex)
|
||||||
|
val bmp = createBitmap(w, h)
|
||||||
|
pdfium.renderPageBitmap(doc, bmp, item.pageIndex, 0, 0, w, h)
|
||||||
|
|
||||||
|
bitmaps.add(bmp)
|
||||||
|
onProgress?.invoke(idx + 1, sorted.size)
|
||||||
|
delay(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val longBitmap = mergeBitmapsVertical(bitmaps)
|
||||||
|
val baseName = inputFile.nameWithoutExtension
|
||||||
|
val extension = if (format.equals("PNG", true)) "png" else "jpg"
|
||||||
|
val compressType = if (format.equals("PNG", true)) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG
|
||||||
|
|
||||||
|
var outFile = File(outputDir, "${baseName}_long.$extension")
|
||||||
|
var i = 1
|
||||||
|
while (outFile.exists()) {
|
||||||
|
outFile = File(outputDir, "${baseName}_long($i).$extension")
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutputStream(outFile).use {
|
||||||
|
longBitmap.compress(compressType, quality, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfium.closeDocument(doc)
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
// 释放内存
|
||||||
|
bitmaps.forEach { it.recycle() }
|
||||||
|
|
||||||
|
return@withContext outFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mergeBitmapsVertical(bitmaps: List<Bitmap>): Bitmap {
|
||||||
|
if (bitmaps.isEmpty()) throw IllegalArgumentException("Bitmap list is empty")
|
||||||
|
|
||||||
|
// 统一宽度为最宽的
|
||||||
|
val maxWidth = bitmaps.maxOf { it.width }
|
||||||
|
|
||||||
|
// 按比例缩放每张,保持宽度一致
|
||||||
|
val scaledBitmaps = bitmaps.map { bmp ->
|
||||||
|
if (bmp.width == maxWidth) bmp
|
||||||
|
else bmp.scale(maxWidth, (bmp.height * (maxWidth / bmp.width.toFloat())).toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
val totalHeight = scaledBitmaps.sumOf { it.height }
|
||||||
|
val mergedBitmap = createBitmap(maxWidth, totalHeight)
|
||||||
|
val canvas = Canvas(mergedBitmap)
|
||||||
|
|
||||||
|
var currentHeight = 0
|
||||||
|
scaledBitmaps.forEach { bmp ->
|
||||||
|
canvas.drawBitmap(bmp, 0f, currentHeight.toFloat(), null)
|
||||||
|
currentHeight += bmp.height
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedBitmap
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,4 +47,10 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/pdf_to_img" />
|
android:text="@string/pdf_to_img" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pdfToLongImgBtn"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/pdf_to_long_img" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -158,6 +158,7 @@
|
|||||||
<string name="unknown_source">Unknown Source</string>
|
<string name="unknown_source">Unknown Source</string>
|
||||||
<string name="img_to_pdf">Image to PDF</string>
|
<string name="img_to_pdf">Image to PDF</string>
|
||||||
<string name="pdf_to_img">PDF to image</string>
|
<string name="pdf_to_img">PDF to image</string>
|
||||||
|
<string name="pdf_to_long_img">PDF to Long image</string>
|
||||||
<string name="convert">CONVERT</string>
|
<string name="convert">CONVERT</string>
|
||||||
<string name="converted_successfully">Converted successfully!</string>
|
<string name="converted_successfully">Converted successfully!</string>
|
||||||
<string name="no_app_to_open_image">No app found to open image</string>
|
<string name="no_app_to_open_image">No app found to open image</string>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user