diff --git a/Common/File/DirListing.cpp b/Common/File/DirListing.cpp index 8091749f15..07ae7f7559 100644 --- a/Common/File/DirListing.cpp +++ b/Common/File/DirListing.cpp @@ -244,7 +244,7 @@ bool GetFilesInDir(const Path &directory, std::vector *files, const ch int beforeFilter = (int)fileList.size(); *files = ApplyFilter(fileList, filter, prefix); std::sort(files->begin(), files->end()); - DEBUG_LOG(Log::IO, "GetFilesInDir: Found %d entries (%d before filter)", (int)files->size(), beforeFilter); + DEBUG_LOG(Log::IO, "GetFilesInDir: Found %d entries (%d before filter). Path: %s", (int)files->size(), beforeFilter, directory.ToVisualString().c_str()); return exists; } diff --git a/UI/AdhocServerScreen.h b/UI/AdhocServerScreen.h index 06ad80a145..cb1fecdcee 100644 --- a/UI/AdhocServerScreen.h +++ b/UI/AdhocServerScreen.h @@ -22,7 +22,7 @@ public: protected: void OnCompleted(DialogResult result) override; bool CanComplete(DialogResult result) override; - virtual UI::Size PopupWidth() const { return 650; } + virtual UI::Size PopupWidth() const override { return 650; } void sendMessage(UIMessage message, const char *value) override; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index d22d8c9e42..4fd3ecd9ce 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1659,8 +1659,13 @@ void GameSettingsScreen::OnChangeBackground(UI::EventParams &e) { } if (!filename.empty()) { + Path src(value); Path dest = GetSysDirectory(DIRECTORY_SYSTEM) / filename; - File::Copy(Path(value), dest); + File::Copy(src, dest); + if (src.FilePathContainsNoCase("temp_import.jpg")) { + INFO_LOG(Log::UI, "Deleting temp file: %s", GetFriendlyPath(src).c_str()); + File::Delete(src); + } } else { g_OSD.Show(OSDType::MESSAGE_ERROR, sy->T("Only JPG and PNG images are supported"), path.GetFilename(), 5.0); } diff --git a/android/src/org/ppsspp/ppsspp/ImageResultProxyActivity.java b/android/src/org/ppsspp/ppsspp/ImageResultProxyActivity.java index 2211aa69c4..4973f83ff1 100644 --- a/android/src/org/ppsspp/ppsspp/ImageResultProxyActivity.java +++ b/android/src/org/ppsspp/ppsspp/ImageResultProxyActivity.java @@ -2,6 +2,8 @@ package org.ppsspp.ppsspp; import android.app.Activity; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import androidx.activity.result.ActivityResultLauncher; @@ -18,17 +20,83 @@ import java.io.OutputStream; public class ImageResultProxyActivity extends AppCompatActivity { public static final String TAG = "PPSSPP"; - private String copyToCache(Uri selectedImage) { + private String copyAndDownscaleToCache(Uri selectedImage) { Log.i(TAG, "Selected image: " + selectedImage); - File tempFile = new File(getExternalCacheDir(), "temp_import.jpg"); + File cacheDir = getExternalCacheDir(); + if (cacheDir == null) { + Log.e(TAG, "External cache directory not available"); + return null; + } + File tempFile = new File(cacheDir, "temp_import.jpg"); - try (InputStream in = getContentResolver().openInputStream(selectedImage); - OutputStream out = new FileOutputStream(tempFile)) { + try { + // First, just get the dimensions. + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + try (InputStream in = getContentResolver().openInputStream(selectedImage)) { + if (in != null) { + BitmapFactory.decodeStream(in, null, options); + } else { + Log.e(TAG, "Failed to open input stream for dimensions"); + return null; + } + } - byte[] buf = new byte[8192]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); + int width = options.outWidth; + int height = options.outHeight; + Log.i(TAG, "Image dimensions: " + width + "x" + height); + + int maxDim = 2048; + if (width <= maxDim && height <= maxDim && width > 0 && height > 0) { + // No downscaling needed, just copy for maximum quality. + try (InputStream in = getContentResolver().openInputStream(selectedImage); + OutputStream out = new FileOutputStream(tempFile)) { + if (in == null) { + Log.e(TAG, "Failed to open input stream for copying"); + return null; + } + byte[] buf = new byte[65536]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } + } else if (width > 0 && height > 0) { + // Downscaling needed. + float ratio = Math.min((float) maxDim / width, (float) maxDim / height); + int targetWidth = Math.round(width * ratio); + int targetHeight = Math.round(height * ratio); + + // Calculate inSampleSize to save memory during decoding. + options.inJustDecodeBounds = false; + options.inSampleSize = 1; + while (width / (options.inSampleSize * 2) >= targetWidth && height / (options.inSampleSize * 2) >= targetHeight) { + options.inSampleSize *= 2; + } + + try (InputStream in = getContentResolver().openInputStream(selectedImage)) { + if (in == null) { + Log.e(TAG, "Failed to open input stream for downscaling"); + return null; + } + Bitmap bitmap = BitmapFactory.decodeStream(in, null, options); + if (bitmap != null) { + Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, true); + try (OutputStream out = new FileOutputStream(tempFile)) { + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out); + } + if (scaledBitmap != bitmap) { + bitmap.recycle(); + } + scaledBitmap.recycle(); + } else { + Log.e(TAG, "Failed to decode bitmap for downscaling"); + return null; + } + } + } else { + Log.e(TAG, "Invalid image dimensions: " + width + "x" + height); + return null; } // Pass the local path back instead of the content:// URI @@ -36,6 +104,7 @@ public class ImageResultProxyActivity extends AppCompatActivity { Log.i(TAG, "Return path: " + returnPath); return returnPath; } catch (IOException e) { + Log.e(TAG, "Error processing image: " + e.getMessage()); e.printStackTrace(); return null; } @@ -49,7 +118,7 @@ public class ImageResultProxyActivity extends AppCompatActivity { Intent pickerIntent = getIntent().getParcelableExtra("picker_intent"); int requestId = getIntent().getIntExtra("request_id", -1); - Log.i(TAG, "Setting up activity launch, requestId = " + requestId); + Log.i(TAG, "Setting up activity launch, requestId = " + requestId + (savedInstanceState == null ? " (new)" : " (recreated)")); ActivityResultLauncher launcher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), @@ -58,7 +127,7 @@ public class ImageResultProxyActivity extends AppCompatActivity { Intent returnIntent = new Intent(this, PpssppActivity.class); if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { - String localPath = copyToCache(result.getData().getData()); + String localPath = copyAndDownscaleToCache(result.getData().getData()); Log.i(TAG, "Putting extra: " + localPath); // Pass the result back to the SingleInstance activity. returnIntent.putExtra("result_path", localPath); @@ -74,6 +143,15 @@ public class ImageResultProxyActivity extends AppCompatActivity { } ); - launcher.launch(pickerIntent); + // Only launch the picker if we are starting fresh. If we're being recreated, + // the ActivityResultRegistry will handle delivering the pending result to the launcher. + if (savedInstanceState == null) { + if (pickerIntent != null) { + launcher.launch(pickerIntent); + } else { + Log.e(TAG, "No picker intent provided"); + finish(); + } + } } }