Why Wasn't a Photo-Editing App Good Enough?
Because color-blind users need correction while they're looking at the world, not three seconds after photographing it — that gap is what pushed the idea toward real-time GPU processing.
About 300 million people worldwide have some form of color vision deficiency. I knew the statistic, but it didn't mean much to me until a friend showed me how he sees the world. Reds looked brown. Greens blurred into yellows. Traffic lights were distinguishable only by position, not color.
I looked at what was available on the App Store. Most "color blind apps" were photo editors — you take a picture, the app processes it, and you see a corrected version. Useful for checking if your socks match, but useless for navigating the real world in real time. I wanted to build something that worked like a live camera viewfinder, correcting colors as you look through your phone. That meant real-time GPU processing, not after-the-fact photo editing.
Why Did the First Prototype Run at 3fps?
Because it ran the daltonization math on the CPU — moving the same algorithm to a GLSL fragment shader took it from 3fps to 60fps overnight, which is what proved the idea could ship.
The first prototype was rough. I started with Flutter's camera plugin and a basic daltonization algorithm running on the CPU. It worked — technically. But at about 3 frames per second, watching through the viewfinder felt like looking through a slideshow. The CPU simply cannot process 2 million pixels per frame fast enough for a smooth experience.
The breakthrough came when I moved the daltonization algorithm to a GLSL fragment shader. Instead of processing pixels one at a time on the CPU, the GPU processes all of them in parallel. The frame rate jumped from 3fps to 60fps overnight. That was the "aha" moment — the moment I knew this could actually become a real product.
Why Did Android Take Weeks Longer Than iOS?
Because iOS hands you a near-RGB format, but Android delivers YUV420
— a completely different plane layout that needs real conversion
math, debugged with no printf available inside a GLSL
shader.
iOS was straightforward. The camera delivers frames in BGRA format, which is essentially RGB with a channel swap. Feed it to the shader, swizzle the channels, done. I had a working iOS prototype in a weekend.
Android was a different story. The camera delivers frames in YUV420 format — a completely different encoding where luminance and chrominance are stored in separate planes with different resolutions. You can't just feed YUV data to an RGB shader. The math is different, the memory layout is different, and getting it wrong means your "color correction" turns the entire feed green or shows corrupted stripes.
I spent weeks on this. Debugging shader output on Android is painful
— there's no printf in GLSL. You're staring at garbled colors trying
to figure out which matrix is wrong or which plane offset is
miscalculated. Eventually, I integrated the
yuv_converter package to handle the YUV-to-RGB
conversion before passing frames to the daltonization shader. It
added a conversion step, but the GPU handles it fast enough to
maintain smooth performance.
Cross-platform development sounds great until you're debugging a GPU shader that works perfectly on iOS but produces psychedelic noise on Android. The camera pipeline is where "write once, run anywhere" meets cold, hard reality.
Why 620 Tests for an Accessibility App?
Because the camera pipeline and shader integration are the riskiest code in the app — that's where the test suite is heaviest, and where a full pipeline refactor stayed safe instead of terrifying.
I committed to test-driven development from the beginning, and I'm glad I did. Color Vision Aid has 620 tests — unit tests for the daltonization math, widget tests for every screen, and integration tests for the camera flow.
Testing Flutter widgets that depend on Provider was tricky at first.
When a widget calls a provider method in initState, the
provider needs to be available before the frame renders. The
solution is wrapping those calls in
addPostFrameCallback, which defers execution until
after the widget tree is built. It's a small pattern, but it
eliminates an entire category of flaky tests.
The test suite also uses fake implementations for every repository
and service, registered through GetIt. Need to test the settings
screen without a real camera? Register a
FakeCameraService. Need to test the correction mode
selector without Firebase? Register a
FakeRemoteConfigRepository. The fakes are defined in a
single fakes.dart file and shared across the entire
test suite.
620 tests might sound excessive for an accessibility app. But those tests caught dozens of regressions during development — especially when I refactored the camera pipeline for Android YUV support. Without the test suite, that refactor would have been terrifying. With it, I had confidence that nothing else broke.
What Actually Went Wrong During App Store Submission?
Fastlane's groups: parameter
triggers an unwanted app-review submission
even for internal-only testing groups — a custom Spaceship-based
helper was the fix, and it's now used across all four of our iOS
apps.
Getting the app onto the App Store involved setting up Fastlane for automated TestFlight distribution. The process should be straightforward: build the IPA, upload to App Store Connect, distribute to testers. In practice, Fastlane has quirks.
The biggest issue was with internal TestFlight distribution.
Fastlane's groups: parameter in
upload_to_testflight has a bug where it calls
post_beta_app_review_submissions even for internal
groups, which triggers an unnecessary review process. I built a
custom distribute_to_internal_testers() helper using
Spaceship (Fastlane's App Store Connect API wrapper) that uploads
with skip_waiting_for_build_processing: true, polls
until the build is VALID, and then adds it to the Internal Testers
group directly. This pattern works so well that I've adopted it
across all four of our iOS apps.
The App Store review itself was smooth. Accessibility apps tend to get a favorable review experience — Apple cares about accessibility, and the reviewers understood what the app does. The only feedback was adding a more detailed privacy nutrition label, which took five minutes to update.
What Does the Backend Actually Do at Launch?
Almost nothing — Firebase Crashlytics for stability and Remote Config for feature toggles is the entire backend; no user accounts, no analytics beyond crash reporting, no data collection.
Color Vision Aid went live on the App Store in February 2026. It's free, with no ads and no tracking. Firebase Crashlytics monitors stability, and Remote Config lets me toggle features without shipping updates. That's the extent of the backend — no user accounts, no analytics beyond crash reporting, no data collection.
I built this app because it solves a real problem for real people. The 8% of men and 0.5% of women who experience color vision deficiency deserve better tools than photo editors with a 3-second processing delay. They deserve to point their phone at the world and see it more clearly, in real time, at 60 frames per second.
What Did Shipping Color Vision Aid Actually Teach Me?
Ship early and iterate on real feedback, put the heaviest test coverage on the scariest code, and build for a real problem instead of a market opportunity — the last one is what makes the other two worth doing.
Three lessons from shipping Color Vision Aid:
- Ship early, iterate later. The first version didn't support all three CVD types. It didn't have a settings screen. It barely had error handling. But it worked, and getting it into testers' hands early produced feedback that shaped everything that came after.
- Test thoroughly, especially the scary parts. The camera pipeline and shader integration are the most complex parts of the app. They're also the most thoroughly tested. The 620 tests aren't evenly distributed — the hardest code has the most coverage.
- Solve real problems. I didn't build Color Vision Aid because it was a good business opportunity. I built it because I saw someone struggle with colors and thought "I can fix that." The best marketing for a utility app is being genuinely useful. Users find it, tell others, and the app grows on its own.
Color Vision Aid is available now on the App Store. If you or someone you know has color vision deficiency, give it a try. It's free, and it just might change how you see the world.