长图功能

This commit is contained in:
ocean 2025-11-04 15:31:01 +08:00
parent f90b57ace5
commit 7208fa3d8a
7 changed files with 156 additions and 16 deletions

View File

@ -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()
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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() {

View File

@ -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
}
}

View File

@ -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>

View File

@ -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>