Did you know that despite WebP images being 26% smaller in size compared to standard PNGs, a shocking number of web portals, government forms, and older software systems still completely reject them? This format fragmentation is a constant, quiet headache for everyday mobile users. A user downloads an image, tries to upload it, and gets hit with a rigid "format not supported" error.
Welcome to the messy reality of digital image formats.
Why I Built PhotoConvert
I noticed this friction happening frequently around me. When faced with an incompatible image format, most people instinctively search the web for "JPG to PNG converter" and upload their personal, sometimes sensitive, photos to ad-riddled, remote servers. This felt like a massive privacy risk and a wildly inefficient use of bandwidth, especially when modern smartphones have more than enough processing power to handle the task locally.
I wanted a tool that lived entirely on the device, worked completely offline, and didn't compromise on image quality. I needed it to handle bulk operations for photographers or designers who need to process dozens of files at once. That motivation led to the development of the PhotoConvert app.
The Tech Stack
For this project, I went native.
- Language: Kotlin
- Framework: Android SDK
- Concurrency: Kotlin Coroutines and Flows
- Storage: Scoped Storage API (to handle Android's modern, restrictive file access)
Technical Challenges
Building a local image processor sounds straightforward until you hit the physical limits of mobile hardware. Here are the main hurdles I had to overcome:
1. The Dreaded OutOfMemory (OOM) Exception
Decoding large images (like a 12-megapixel photo from a modern smartphone camera) to convert them into a different format takes a massive amount of RAM. If a user selects 20 high-resolution images for batch conversion, loading them all into memory simultaneously will instantly crash the app. I had to implement strict memory management, processing images sequentially and aggressively garbage-collecting bitmap objects the moment they were written to disk.
2. Navigating Format Quirks
Android's native Bitmap.CompressFormat handles JPG, PNG, and WebP reasonably well out of the box. However, handling older or more complex formats like BMP and GIF requires specific workarounds. Furthermore, converting from a format that supports transparency (like PNG) to one that doesn't (like JPG) often results in a solid black background where the transparent pixels used to be. To fix this, I had to programmatically draw a white canvas behind the image before compressing it to a JPG, ensuring the final image looked natural.
3. Taming Scoped Storage
Android's Scoped Storage rules mean apps can no longer just read and write anywhere on the device. Handling batch selection via the Storage Access Framework (SAF) and ensuring the app had the correct permissions to write the converted files back to the user's gallery without prompting them 50 individual times required careful orchestration of Android's MediaStore APIs.
4. Keeping the UI Responsive
Running heavy file I/O and bitmap compression on the main thread is a guaranteed way to freeze your app and trigger an Application Not Responding (ANR) dialog. I heavily utilized Kotlin Coroutines, specifically Dispatchers.IO, to push all the heavy lifting to background threads. Using Kotlin Flows allowed me to emit progress updates back to the UI seamlessly, giving users a real-time progress bar during batch conversions.
Lessons Learned
- Never trust the file extension: Just because a file ends in
.pngdoesn't mean it actually is one. I learned to always check the file's magic numbers (mime type) before attempting to decode it to prevent silent failures. - Offline-first builds trust: Users appreciate apps that don't require an internet connection for tasks that shouldn't need one. It builds immense trust, especially when handling personal media.
- Coroutines are powerful, but cancellation is tricky: Managing the state when a user decides to hit the "Cancel" button halfway through a batch job of 100 images requires ensuring your coroutine scopes are properly structured so background work actually stops when requested.
Conclusion
Building PhotoConvert was a deep dive into the intricacies of Android file management and memory optimization. It reinforced the idea that sometimes the most useful tools are the ones that simply fix a small, recurring annoyance without adding unnecessary complexity or demanding an internet connection.
If you've ever been frustrated by format compatibility on your phone, you can give it a try. It handles JPG, PNG, WebP, GIF, and BMP natively on your device.
Check it out on Google Play or visit the project page. Let me know what you think!
Top comments (0)