The Red channel is taken from the Left view; Green and Blue channels are taken from the Right view.
An idea that started as a nostalgic curiosity: bringing analog stereoscopic cinema into the palms of modern users. Anaglyph 3D—red/cyan composite images that separate views by color—was one of the earliest and most accessible ways to create a stereoscopic experience. It requires only two slightly offset images (left and right) and inexpensive complementary glasses. The project began from a simple question: could an Android app replicate the old-school anaglyph effect in real time for video playback while taking advantage of modern phone hardware, sensors, and UX expectations?
package com.example.anaglyph3dimport android.content.Intent import android.net.Uri import android.opengl.GLSurfaceView import android.os.Bundle import android.provider.OpenableColumns import android.widget.Button import androidx.appcompat.app.AppCompatActivity import android.media.MediaPlayer import android.view.Surface anaglyph 3d video player for android
class MainActivity : AppCompatActivity()
private lateinit var glSurfaceView: GLSurfaceView private lateinit var renderer: AnaglyphRenderer private var mediaPlayer: MediaPlayer? = null private var videoUri: Uri? = null private val PICK_VIDEO_REQUEST = 1 override fun onCreate(savedInstanceState: Bundle?) super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) glSurfaceView = findViewById(R.id.glSurfaceView) glSurfaceView.setEGLContextClientVersion(2) renderer = AnaglyphRenderer(this) glSurfaceView.setRenderer(renderer) glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY findViewById<Button>(R.id.btnPick).setOnClickListener pickVideo() findViewById<Button>(R.id.btnPlayPause).setOnClickListener mediaPlayer?.let mp -> if (mp.isPlaying) mp.pause() else mp.start() private fun pickVideo() val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply addCategory(Intent.CATEGORY_OPENABLE) type = "video/*" startActivityForResult(intent, PICK_VIDEO_REQUEST) override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) super.onActivityResult(requestCode, resultCode, data) if (requestCode == PICK_VIDEO_REQUEST && resultCode == RESULT_OK) data?.data?.let uri -> videoUri = uri contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) setupMediaPlayer(uri) private fun setupMediaPlayer(uri: Uri) mediaPlayer?.release() mediaPlayer = MediaPlayer().apply setDataSource(contentResolver.openFileDescriptor(uri, "r")?.fileDescriptor) prepareAsync() setOnPreparedListener start() val surface = Surface(renderer.surfaceTexture) setSurface(surface) surface.release() glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY setOnVideoSizeChangedListener _, width, height -> renderer.setVideoSize(width, height) override fun onDestroy() super.onDestroy() mediaPlayer?.release() mediaPlayer = null
@Test public void testAnaglyphConversion() Bitmap left = createSolidColorBitmap(Color.RED); Bitmap right = createSolidColorBitmap(Color.BLUE);Bitmap result = anaglyphProcessor.convert(left, right); // Red channel should come from left int pixel = result.getPixel(0, 0); assertEquals(Color.red(pixel), Color.red(left.getPixel(0,0))); // Green/Blue from right assertEquals(Color.green(pixel), Color.green(right.getPixel(0,0))); assertEquals(Color.blue(pixel), Color.blue(right.getPixel(0,0)));