长图功能
This commit is contained in:
parent
f90b57ace5
commit
7208fa3d8a
@ -106,10 +106,16 @@ class PdfPickerActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
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.PDF_TO_IMAGES -> {
|
||||
val intent = PdfToImageActivity.createIntent(this, pdf.filePath)
|
||||
val intent = PdfToImageActivity.createIntent(this, pdf.filePath,
|
||||
PdfPickerSource.PDF_TO_IMAGES)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
@ -112,6 +112,7 @@ class PdfResultActivity : BaseActivity() {
|
||||
private var exitDialog: PromptDialogFragment? = null
|
||||
private val pdfRepository = getRepository()
|
||||
private lateinit var pdfScanner: PdfScanner
|
||||
private var openedExternal = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -262,7 +263,8 @@ class PdfResultActivity : BaseActivity() {
|
||||
}
|
||||
val unlockPassword = intent.getStringExtra(EXTRA_LOCK_UNLOCK_PASSWORD) ?: ""
|
||||
runOnUiThread {
|
||||
binding.congratulationsDesc.text = getString(R.string.remove_password_successfully)
|
||||
binding.congratulationsDesc.text =
|
||||
getString(R.string.remove_password_successfully)
|
||||
}
|
||||
PdfSecurityUtils.removePasswordFromPdfWithProgress(
|
||||
filepath, unlockPassword
|
||||
@ -402,15 +404,15 @@ class PdfResultActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
binding.okBtn.setOnClickListener {
|
||||
if (source == PdfPickerSource.PDF_TO_IMAGES) {
|
||||
if (source == PdfPickerSource.PDF_TO_IMAGES || source == PdfPickerSource.TO_LONG_IMAGE) {
|
||||
val selectedItem = adapter.getSelectedItem()
|
||||
selectedItem?.let {
|
||||
val string = AppUtils.openImageFile(this, File(selectedItem.filePath))
|
||||
if (string.isNotEmpty()) {
|
||||
showToast(string)
|
||||
}
|
||||
openedExternal = true
|
||||
}
|
||||
finish()
|
||||
} else {
|
||||
val selectedItem = adapter.getSelectedItem()
|
||||
selectedItem?.let {
|
||||
@ -422,6 +424,13 @@ class PdfResultActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (openedExternal) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
isProcessing = false
|
||||
|
||||
@ -2,6 +2,7 @@ package com.all.pdfreader.pro.app.ui.act
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
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.model.PdfPageItem
|
||||
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.dialog.PdfPasswordProtectionDialogFragment
|
||||
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.toUnderscoreDateTime
|
||||
import com.all.pdfreader.pro.app.util.PdfUtils
|
||||
import com.gyf.immersionbar.ImmersionBar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
|
||||
class PdfToImageActivity : BaseActivity() {
|
||||
override val TAG: String = "PdfToImageActivity"
|
||||
|
||||
|
||||
var currentPassword: String? = null//只会选择一个文件。直接进行密码传递
|
||||
private lateinit var source: PdfPickerSource
|
||||
|
||||
companion object {
|
||||
private const val EXTRA_PDF_PATH = "extra_pdf_path"
|
||||
|
||||
fun createIntent(context: Context, filePath: String): Intent {
|
||||
private const val EXTRA_SOURCE = "extra_source"
|
||||
fun createIntent(context: Context, filePath: String, source: PdfPickerSource): Intent {
|
||||
return Intent(context, PdfToImageActivity::class.java).apply {
|
||||
putExtra(EXTRA_PDF_PATH, filePath)
|
||||
putExtra(EXTRA_SOURCE, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -52,6 +54,7 @@ class PdfToImageActivity : BaseActivity() {
|
||||
.navigationBarColor(R.color.bg_color).init()
|
||||
filePath = intent.getStringExtra(EXTRA_PDF_PATH)
|
||||
?: throw IllegalArgumentException("PDF file hash is required")
|
||||
source = getSerializableOrDefault(EXTRA_SOURCE, PdfPickerSource.NONE)
|
||||
if (filePath.isEmpty()) {
|
||||
showToast(getString(R.string.file_not))
|
||||
finish()
|
||||
@ -92,13 +95,23 @@ class PdfToImageActivity : BaseActivity() {
|
||||
}
|
||||
binding.continueNowBtn.setOnSingleClickListener {
|
||||
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,
|
||||
filepath = filePath,
|
||||
list = ArrayList(selectedPages),
|
||||
source = PdfPickerSource.PDF_TO_IMAGES,
|
||||
password = currentPassword
|
||||
)
|
||||
} else {
|
||||
PdfResultActivity.createIntentPdfToImageActivityToResult(
|
||||
context = this,
|
||||
filepath = filePath,
|
||||
list = ArrayList(selectedPages),
|
||||
source = PdfPickerSource.TO_LONG_IMAGE,
|
||||
password = currentPassword
|
||||
)
|
||||
}
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
@ -172,4 +185,16 @@ class PdfToImageActivity : BaseActivity() {
|
||||
override fun 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)
|
||||
startActivity(intent)
|
||||
}
|
||||
binding.pdfToLongImgBtn.setOnClickListener {
|
||||
val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.TO_LONG_IMAGE)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openImagePicker() {
|
||||
|
||||
@ -3,6 +3,7 @@ package com.all.pdfreader.pro.app.util
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import androidx.core.graphics.createBitmap
|
||||
@ -24,6 +25,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import androidx.core.graphics.scale
|
||||
|
||||
object PdfUtils {
|
||||
|
||||
@ -419,4 +421,91 @@ object PdfUtils {
|
||||
fd.close()
|
||||
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: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>
|
||||
@ -158,6 +158,7 @@
|
||||
<string name="unknown_source">Unknown Source</string>
|
||||
<string name="img_to_pdf">Image to PDF</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="converted_successfully">Converted successfully!</string>
|
||||
<string name="no_app_to_open_image">No app found to open image</string>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user