Android Custom Camera Preview

by QWeb Ltd

The Android Custom Camera Preview plugin provides simple functions to easily show a live, full screen camera preview and optionally overlay it with a centered image for custom frame or crosshair graphics, and/or text for instructions/feedback to the user.

The plugin also provides a function for taking photos while the preview is running. Photos are saved to the device cache directory and the filename is returned to Solar2D for custom handling.

The plugin can be used to return a list of available front facing, back facing, or external camera devices, and any connected camera device can be used for both the preview and photo captures.

Integration

Activate this plugin from the link on the right, then copy the generated build.settings content into your project as instructed.

Load the Andriod Custom Camera Preview library into your Solar2D project and request camera access permissions from the user:

	local cameraView = require "plugin.androidCustomCameraPreview"

	local hasAccessToCamera, hasCamera = media.hasSource( media.Camera )
	if(hasAccessToCamera == false) then
		if(hasCamera == true and native.canShowPopup( "requestAppPermission" )) then
			native.showPopup( "requestAppPermission", { appPermission="Camera" } )
		end

		display.newText( { text = "After authorising camera permissions, restart this app.", x = display.contentCenterX, y = display.contentCenterY, width = 200, height = 0, font = native.systemFont, fontSize = 20, align = "center" } )
	end

That's it! You can now use any of the below functions within your Solar2D game or app.

Functions

start(string camera_id)

Starts a full screen preview using the device passed as camera_id, or of the back-facing camera if no device is specified. The preview overlays all Solar2D display objects.

If "back" or "front" is passed as camera_id, the plugin will automatically detect an appropriate device to use.

	local cameraView = require "plugin.androidCustomCameraPreview"

	local hasAccessToCamera, hasCamera = media.hasSource( media.Camera )
	if(hasAccessToCamera == false) then
		if(hasCamera == true and native.canShowPopup( "requestAppPermission" )) then
			native.showPopup( "requestAppPermission", { appPermission="Camera" } )
		end

		display.newText( { text = "After authorising camera permissions, restart this app.", x = display.contentCenterX, y = display.contentCenterY, width = 200, height = 0, font = native.systemFont, fontSize = 20, align = "center" } )
	else
		cameraView.start("front")
	end

stop()

Stops the preview and returns to your Solar2D display.

	local cameraView = require "plugin.androidCustomCameraPreview"

	local hasAccessToCamera, hasCamera = media.hasSource( media.Camera )
	if(hasAccessToCamera == false) then
		if(hasCamera == true and native.canShowPopup( "requestAppPermission" )) then
			native.showPopup( "requestAppPermission", { appPermission="Camera" } )
		end

		display.newText( { text = "After authorising camera permissions, restart this app.", x = display.contentCenterX, y = display.contentCenterY, width = 200, height = 0, font = native.systemFont, fontSize = 20, align = "center" } )
	else
		local mainDisplayGroup = display.newGroup()
		local button

		cameraView.start()

		local function handleTaps(event)
			cameraView.stop()
			display.remove(button)
		end

		-- Fill the screen with a button to capture taps while the camera is displayed. Can't actually see this for the camera but it'll still receive events.
		button = display.newRect( mainDisplayGroup, display.contentCenterX, display.contentCenterY, display.contentWidth + ((display.screenOriginX / -1) * 2), display.contentHeight + ((display.screenOriginY / -1) * 2) )
		button:setFillColor(0, 0, 0)
		button:addEventListener('tap', handleTaps)
	end

cameras()

Returns a table of supported camera devices attached either as native front facing or back facing cameras, or as external devices.

The returned table includes device ID, the direction the lens is facing, and the camera resolution.

	local cameraView = require "plugin.androidCustomCameraPreview"

	local hasAccessToCamera, hasCamera = media.hasSource( media.Camera )
	if(hasAccessToCamera == false) then
		if(hasCamera == true and native.canShowPopup( "requestAppPermission" )) then
			native.showPopup( "requestAppPermission", { appPermission="Camera" } )
		end

		display.newText( { text = "After authorising camera permissions, restart this app.", x = display.contentCenterX, y = display.contentCenterY, width = 200, height = 0, font = native.systemFont, fontSize = 20, align = "center" } )
	else
		local cameras = cameraView.cameras()

		for k,v in pairs(cameras) do
			print("Camera " .. k .. " id = " .. v.id)
			print("Camera " .. k .. " facing = " .. v.facing)
			print("Camera " .. k .. " resolution width = " .. v.resolution.width)
			print("Camera " .. k .. " resolution height = " .. v.resolution.height)

			if(v.facing == "back") then
				cameraToUse = v.id
			end
		end

		if(cameraToUse ~= nil) then
			cameraView.start(cameraToUse)
		end
	end

status()

Receive the Android Custom Camera Preview current status. Return value will be either "waiting" or "running".

	local cameraView = require "plugin.androidCustomCameraPreview"

	local hasAccessToCamera, hasCamera = media.hasSource( media.Camera )
	if(hasAccessToCamera == false) then
		if(hasCamera == true and native.canShowPopup( "requestAppPermission" )) then
			native.showPopup( "requestAppPermission", { appPermission="Camera" } )
		end

		display.newText( { text = "After authorising camera permissions, restart this app.", x = display.contentCenterX, y = display.contentCenterY, width = 200, height = 0, font = native.systemFont, fontSize = 20, align = "center" } )
	else
		local mainDisplayGroup = display.newGroup()
		local button

		print("Status before starting: " .. cameraView.status())

		cameraView.start()

		print("Status after starting: " .. cameraView.status())

		local function handleTaps(event)
			cameraView.stop()
			display.remove(button)

			print("Status after stopping: " .. cameraView.status())
		end

		-- Fill the screen with a button to capture taps while the camera is displayed. Can't actually see this for the camera but it'll still receive events.
		button = display.newRect( mainDisplayGroup, display.contentCenterX, display.contentCenterY, display.contentWidth + ((display.screenOriginX / -1) * 2), display.contentHeight + ((display.screenOriginY / -1) * 2) )
		button:setFillColor(0, 0, 0)
		button:addEventListener('tap', handleTaps)
	end

text(string position, string message)

Sets a message to appear either at position "top" or "bottom" of the camera preview.

Messages are shown in white with a subtle black outline shadow for optimal visibility regardless of what the camera is showing. Text displayed at the top outputs at a maximum font size of 30pt, and text displayed at the bottom outputs with a maximum font size of 20pt. On devices running Android Oreo (Android 8.0, SDK 26) or later, text size is adjusted for best-fit. On devices running earlier versions the text is output at maximum size.

	local cameraView = require "plugin.androidCustomCameraPreview"

	local hasAccessToCamera, hasCamera = media.hasSource( media.Camera )
	if(hasAccessToCamera == false) then
		if(hasCamera == true and native.canShowPopup( "requestAppPermission" )) then
			native.showPopup( "requestAppPermission", { appPermission="Camera" } )
		end

		display.newText( { text = "After authorising camera permissions, restart this app.", x = display.contentCenterX, y = display.contentCenterY, width = 200, height = 0, font = native.systemFont, fontSize = 20, align = "center" } )
	else
		local mainDisplayGroup = display.newGroup()
		local button

		cameraView.start()
		cameraView.text("top", "Android Custom Camera Preview for Solar2D")
		cameraView.text("bottom", "Hello World!")

		local function handleTaps(event)
			cameraView.stop()
			display.remove(button)
		end

		-- Fill the screen with a button to capture taps while the camera is displayed. Can't actually see this for the camera but it'll still receive events.
		button = display.newRect( mainDisplayGroup, display.contentCenterX, display.contentCenterY, display.contentWidth + ((display.screenOriginX / -1) * 2), display.contentHeight + ((display.screenOriginY / -1) * 2) )
		button:setFillColor(0, 0, 0)
		button:addEventListener('tap', handleTaps)
	end

image(string filename)

Loads an image from the device cache directory and centers to the camera preview. Images are scaled to fit, so for best results go for a high resolution square canvas.

Images must be moved to the cache directory first and as per the Solar2D documentation, should therefore be named as a .txt file to prevent the build process from packaging them into your binary.

	local cameraView = require "plugin.androidCustomCameraPreview"

	local hasAccessToCamera, hasCamera = media.hasSource( media.Camera )
	if(hasAccessToCamera == false) then
		if(hasCamera == true and native.canShowPopup( "requestAppPermission" )) then
			native.showPopup( "requestAppPermission", { appPermission="Camera" } )
		end

		display.newText( { text = "After authorising camera permissions, restart this app.", x = display.contentCenterX, y = display.contentCenterY, width = 200, height = 0, font = native.systemFont, fontSize = 20, align = "center" } )
	else
		local mainDisplayGroup = display.newGroup()
		local button

		-- Moves crosshair.png.txt to the cache directory + renames to crosshair.png, so that we can tell cameraPreview to open it
		local rfh = io.open( system.pathForFile("crosshair.png.txt"), "rb" )
		local wfh = io.open( system.pathForFile("crosshair.png", system.CachesDirectory), "wb" )
		local data = rfh:read( "*a" )
		wfh:write( data )
		rfh:close()
		wfh:close()

		cameraView.start()
		cameraView.image(system.pathForFile("crosshair.png", system.CachesDirectory))

		local function handleTaps(event)
			cameraView.stop()
			display.remove(button)
		end

		-- Fill the screen with a button to capture taps while the camera is displayed. Can't actually see this for the camera but it'll still receive events.
		button = display.newRect( mainDisplayGroup, display.contentCenterX, display.contentCenterY, display.contentWidth + ((display.screenOriginX / -1) * 2), display.contentHeight + ((display.screenOriginY / -1) * 2) )
		button:setFillColor(0, 0, 0)
		button:addEventListener('tap', handleTaps)
	end

photo()

Captures a photo from the Camera Preview. Camera Preview must first be started.

Returns the filename of the resulting photo saved to the caches directory, but you must wait for the file to be fully written first. Refer to the photoStatus() function for this.

Some devices will freeze the preview image while the photo is being written, and the stop() function may be delayed until after the write completes.

	local cameraView = require "plugin.androidCustomCameraPreview"

	local hasAccessToCamera, hasCamera = media.hasSource( media.Camera )
	if(hasAccessToCamera == false) then
		if(hasCamera == true and native.canShowPopup( "requestAppPermission" )) then
			native.showPopup( "requestAppPermission", { appPermission="Camera" } )
		end

		display.newText( { text = "After authorising camera permissions, restart this app.", x = display.contentCenterX, y = display.contentCenterY, width = 200, height = 0, font = native.systemFont, fontSize = 20, align = "center" } )
	else
		local mainDisplayGroup = display.newGroup()
		local button

		cameraView.start()

		local function handleTaps(event)
			local pic = cameraView.photo()
			local file = system.pathForFile(pic, system.CachesDirectory)

			if(file ~= nil) then
				-- Wait until the photo file has finished being written
				while(cameraView.photoStatus() ~= "saved") and (cameraView.photoStatus() ~= "error") do
				end

				-- Show the resulting capture and scale to fit
				local capturedImage = display.newImage(mainDisplayGroup, pic, system.CachesDirectory, display.contentCenterX, display.contentCenterY)
				local scale = (display.contentWidth / 2) / capturedImage.width
				capturedImage:scale(scale, scale)
			end

			cameraView.stop()
			display.remove(button)
		end

		-- Fill the screen with a button to capture taps while the camera is displayed. Can't actually see this for the camera but it'll still receive events.
		button = display.newRect( mainDisplayGroup, display.contentCenterX, display.contentCenterY, display.contentWidth + ((display.screenOriginX / -1) * 2), display.contentHeight + ((display.screenOriginY / -1) * 2) )
		button:setFillColor(0, 0, 0)
		button:addEventListener('tap', handleTaps)
	end

photoStatus()

Receive the Android Custom Camera Preview current photo-writing status. Return value will be either "waiting", "writing", "saved", or "error".

When calling photo(), Android Custom Camera Preview will return the filename that the photo will be written to immediately, but it can take a few seconds for the file write to complete, depending on the camera resolution and the file access write speed. You should therefore wait for photoStatus() to return "saved" before attempting to use this file.

	local cameraView = require "plugin.androidCustomCameraPreview"

	local hasAccessToCamera, hasCamera = media.hasSource( media.Camera )
	if(hasAccessToCamera == false) then
		if(hasCamera == true and native.canShowPopup( "requestAppPermission" )) then
			native.showPopup( "requestAppPermission", { appPermission="Camera" } )
		end

		display.newText( { text = "After authorising camera permissions, restart this app.", x = display.contentCenterX, y = display.contentCenterY, width = 200, height = 0, font = native.systemFont, fontSize = 20, align = "center" } )
	else
		local mainDisplayGroup = display.newGroup()
		local button

		cameraView.start()

		local function handleTaps(event)
			local pic = cameraView.photo()
			local file = system.pathForFile(pic, system.CachesDirectory)

			if(file ~= nil) then
				-- Wait until the photo file has finished being written
				while(cameraView.photoStatus() ~= "saved") and (cameraView.photoStatus() ~= "error") do
				end

				-- Show the resulting capture and scale to fit
				local capturedImage = display.newImage(mainDisplayGroup, pic, system.CachesDirectory, display.contentCenterX, display.contentCenterY)
				local scale = (display.contentWidth / 2) / capturedImage.width
				capturedImage:scale(scale, scale)
			end

			cameraView.stop()
			display.remove(button)
		end

		-- Fill the screen with a button to capture taps while the camera is displayed. Can't actually see this for the camera but it'll still receive events.
		button = display.newRect( mainDisplayGroup, display.contentCenterX, display.contentCenterY, display.contentWidth + ((display.screenOriginX / -1) * 2), display.contentHeight + ((display.screenOriginY / -1) * 2) )
		button:setFillColor(0, 0, 0)
		button:addEventListener('tap', handleTaps)
	end

Trusted vendor

$4.00

Log in now to purchase this plugin from QWeb Ltd.

Once you've activated this plugin, appropriate build.settings code will be generated for you to copy into your Solar2D project. Download links will also be available for inclusion into a Solar2D Native project.

Latest Solar2D contributions by QWeb Ltd view all