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.
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.
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
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
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
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
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
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
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
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