Open-source project
AhmadHassan-BTed/MotionShot avatar
AhmadHassan-BTed/MotionShot

MotionShot: a stroboscopic action camera that composites a burst on device

Camera app that freezes fast motion sequences into a single stroboscopic photo; powered by on-device computer vision, 2D stabilization, and manual exposure controls

2,606 stars0 forksKotlinNOASSERTION

At a glance

What is it?
MotionShot is a Kotlin Android app that records a short burst of frames and freezes it into a single multi-pose composite, using 2D translation stabilization and chromaticity-based motion detection on the phone. Everything runs locally, with no cloud service and no neural network in the loop.
Who is it for?
MotionShot suits an Android shooter who wants the multi-pose freeze-frame effect that Sony's old Motion Shot cameras produced, and who is willing to tune shutter speed, ISO and frame count manually. It is the wrong choice for a phone with a shaky tripod budget of 16 pixels of translation, for video work, or for a team that needs a stable published version, since the repository has no GitHub releases and its last push was on 2026-08-05.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 79 days ago.
What is it written in?
Mainly Kotlin, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why a burst becomes a single stroboscopic frame

MotionShot is a camera app for Android that turns a short sequence of fast movement into one picture showing several poses at once. The README frames the problem in the terms of a sprint, a dancer's spin or a child's jump: human movement is transient, and it is gone within fractions of a second. The app was inspired by Sony's legacy Motion Shot technology, and the effect it produces is a multi-pose composite image synthesised on the device in real time.

Two constraints shape the whole design. The app operates entirely offline, with no cloud latency, and it does not lean on heavy AI neural networks. Everything the camera needs to decide what moved and how to align it runs locally on the phone, in ordinary image processing code. The stated result is an aligned visual trajectory, sharp and stabilized, captured in full sensor resolution rather than downsampled for a model.

For a reader weighing this against a conventional camera app, the trade is clear. You give up the automatic decisions a phone camera makes well, and in exchange you get an effect that is hard to reach with a still image pipeline. The project badge describes the status as Production, and the app is built with Jetpack Compose, CameraX and Camera2 interop, targeting Android API 24 and above with Kotlin 2.2.

The manual controls: timer window, frame count, shutter and ISO

The specification table in the README is the clearest statement of what the camera exposes to the photographer, and nearly every parameter is a deliberate choice rather than an automatic one.

The timer window is selectable as 1s, 2s, 5s or a custom value between 1 and 60 seconds, and it acts as the deadline for the whole capture. The frame count is 5, 15, 25 or a custom 2 to 50, described as trajectory density sampling points, which tells you how many poses end up in the composite. Shutter speed offers Auto or a logarithmic manual range from 1/30s to 1/8000s, and ISO offers Auto or a continuous range from ISO 50 to ISO 102,400. ISO is described as a continuous sensor gain boost, and the reason it reaches that far is that a 1/8000s exposure needs the gain to keep the frame bright.

Two implementation parameters sit alongside them. Stabilization is a 2D translation search of plus or minus 16 pixels, aimed at sub-5ms background micro-jitter alignment. Blending uses PorterDuff.Mode.SRC_OVER for sequential alpha layer compositing, and persistence is through SharedPreferences, so the photographer's parameters are restored across sessions.

The five stage pipeline from burst to composite

Each captured sequence moves through a zero-allocation processing pipeline, and the README draws the five stages in order. The raw frame burst enters first, then 2D translation alignment in the ImageStabilizer, then normalized chromaticity differencing in the FrameDifferencer, then a 3x3 morphological opening in MorphologyOps, then soft contour edge feathering, and finally sequence alpha decay in the CanvasCompositor, which produces the stroboscopic composite.

The ordering makes sense. Alignment has to happen before differencing, because a moving camera changes every pixel and would drown the motion signal. Differencing has to happen before morphology, because the morphological opening is there to remove speckle noise from the mask rather than to define the mask. Feathering softens the hard contour of the moving subject, and the alpha decay is what makes earlier poses fade and later poses dominate, which is the visual signature of a stroboscopic composite.

The pipeline's zero-allocation claim matters on mobile, where the burst buffer and several full-resolution masks are live at once. The README attributes the final composition to native Canvas PorterDuff stamping, so the composite is built by the platform's own compositor rather than by pixel loops in Kotlin.

Motion detection that ignores exposure changes

The detection stage is where most burst cameras go wrong, and MotionShot's answer is specific. Instead of comparing raw pixel values, the FrameDifferencer evaluates r, g color space ratios together with BT.601 luminance separation. The README states that this makes motion detection immune to camera exposure shifts and shadows.

That claim is plausible for a reason worth naming. A camera that auto-adjusts exposure between frames changes the brightness of the entire image, which a raw difference turns into a full-frame change. Normalising chromaticity before comparing removes the brightness term, so the mask responds to the subject rather than to the auto-exposure. The corollary is that the feature has to be off or badly calibrated for the technique to fail, which is a reason to look at the mask rather than trust the composite at first.

The stabilisation stage is the other technical centrepiece. The ImageStabilizer correlates background grid pixels across frames and locks static elements in place to remove camera shake, searching a translation of plus or minus 16 pixels. It corrects hand shake, and nothing else: the alignment model has no rotation, no zoom change and no perspective term.

Clean architecture around a pluggable MotionEngine

The architecture diagram separates four concerns. The presentation layer is Jetpack Compose, with MainActivity leading to CameraScreen, which holds the viewfinder, the capture controls sheet with its sliders, and the Step1ResultView flipbook gallery. The domain layer holds MotionShotViewModel, which talks to a PreferencesRepository and to the MotionEngine interface. The engine implementations are FastDifferenceMotionEngine plus ImageStabilizer, FrameDifferencer, MorphologyOps and CanvasCompositor. The hardware layer is CameraX and Camera2, with a FrameAnalyzer, a native C++ YuvToRgb conversion and Camera2Interop for manual control.

The MotionEngine interface and its MotionEngineFactory exist so the engine can be swapped without touching UI state logic. For a project like this, that is the seam that matters most, because the algorithms are the part most likely to be replaced.

The capture lifecycle is equally explicit. Tapping capture calls onCaptureToggle on the view model, which locks a start time and sets a hard deadline of T seconds. The loop then divides that window into T divided by N minus one slices, setting shouldCapture on the analyzer and waiting for frameChannel.receive() under a time-bounded timeout guard. The frames are handed to the engine, and the composite bitmap comes back to be shown as an interactive flipbook.

Local configuration and the build entry points

The repository is a standard Gradle project with build.gradle.kts, settings.gradle.kts, gradle.properties, a gradle/ wrapper directory with gradlew and gradlew.bat, and an app/ module, plus docs/, CHANGELOG.md, CONTRIBUTING.md and an .editorconfig. Local environment settings live in a .env.example template, which documents the keys the build reads:

bash
APP_NAME=MotionShot
BUILD_TYPE=debug
DEFAULT_CAMERA_RESOLUTION=1920x1080
ENABLE_HARDWARE_STABILIZATION=true
LOG_LEVEL=DEBUG

The resolution default of 1920x1080 is worth noting, because the app is described as capturing in full sensor resolution and the template defaults to 1080p rather than the maximum the sensor offers. A photographer who wants the highest resolution capture would need to change this value rather than trust the default.

There is no publishing pipeline described in the README and no GitHub release, so the application is built from the repository with Gradle rather than fetched from a store listing, and the repository has no homepage set.

Limits: translation-only alignment, gain noise and licence ambiguity

Three constraints are worth weighing before you plan a shoot around the app.

The first is that the alignment model is a 2D translation search with a plus or minus 16 pixel range. It removes handheld shake, but it does not correct rotation, zoom, or a subject moving in depth relative to the background, so a shot with a moving camera and a moving subject will composite with visible misregistration.

The second is the exposure floor. Reaching 1/8000s in dim light means pushing ISO to 102,400, and the gain boost that keeps the frame bright also amplifies sensor noise, which no later processing step can undo. In practice the extreme settings need real light.

The third is the licence. The README badge says MIT, while the project metadata reports a custom licence that GitHub cannot classify, and the LICENSE file at the repository root is the authoritative text. That disagreement is exactly the kind to resolve before shipping, rather than after. The project also publishes no GitHub releases, and its last push was on 2026-08-05, so a build today is a build of the current main branch rather than a tagged artefact.

Editorial conclusion

MotionShot suits an Android shooter who wants the multi-pose freeze-frame effect that Sony's old Motion Shot cameras produced, and who is willing to tune shutter speed, ISO and frame count manually. It is the wrong choice for a phone with a shaky tripod budget of 16 pixels of translation, for video work, or for a team that needs a stable published version, since the repository has no GitHub releases and its last push was on 2026-08-05. Verify first that the licence file at the repository root matches what you expect, because the README badge says MIT while the project metadata reports a custom licence, and then check that your device reaches the 1/8000s and ISO 102400 settings before you plan a shoot around them.

Frequently asked questions

Does MotionShot send photos or videos to a server?

No. The README states that the app operates entirely offline with zero cloud latency and does not rely on heavy AI neural networks, and all processing, including the composite, happens on the device.

Which Android versions can run MotionShot?

The platform badge gives Android API 24 and above, with Kotlin 2.2 and a Jetpack Compose interface. Capture goes through CameraX with Camera2 interop for manual control.

How do I control the shutter speed and ISO in MotionShot?

Both are manual sliders in the capture controls, with shutter speeds from 1/30s to 1/8000s and ISO from 50 to 102,400, alongside Auto positions. Parameters persist between sessions through SharedPreferences.

How does MotionShot avoid composite ghosting from camera shake?

The ImageStabilizer correlates background grid pixels across frames and searches a 2D translation of plus or minus 16 pixels, aligning static elements before the motion mask is computed. The correction covers translation only, so rotation and zoom changes are not compensated.

Official sources

  1. AhmadHassan-BTed/MotionShot on GitHub
  2. Issues
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ahmadhassan-bted-motionshot.svg)](https://hysenlabs.com/projects/ahmadhassan-bted-motionshot)

Was this useful?