I want my Clojure program to "look out at the world" through the camera. I found a Github project that does the work and is even written in Java. I got it to work using a 3-line piece of Java code. Then I thought, "I'll just convert it to Clojure using Java interop and go from there." But I don't know how.
Here's the Java code that read the camera, and wrote a one-frame JPG file to disk:
import com.github.sarxos.webcam.Webcam;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
public class WebcamTutorial {
public static void main (String[] args) {
Webcam webcam = Webcam.getDefault();
webcam.open();
try {
ImageIO.write(webcam.getImage(), "JPG", new File("C:\\Users\\Joe User\\Desktop\\firstCapture.JPG"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Here's my Clojure attempt at doing the same thing:
(ns webcam2.core
(:import java.io.File)
(:import java.io.IOException)
(:import javax.imageio.ImageIO)
(:import com.github.sarxos.webcam.Webcam)
(defn -main []
((let [webcam (Webcam. (getDefault.))]
(webcam. open.)
(ImageIO.write. webcam. getImage. "JPG", new .File ("C:\\Users\\Joe User\\Desktop\\firstClojureCapture.JPG"))
)
)
)
)
I'm getting "Unused Imports
" errors on the 4 :import
statements. And most vexing, I also get the error
com.github.sarxos.webcam.Webcam
cannot be resolved
I do not know how to import a Github project into Clojure, or even if such a thing is possible.
In Mac, I couldn't make it work because I have arm64 architecture (M3).
So I tried it in ubuntu and it worked.
The detailed steps to make java run were:
# test that java is running
java -version
openjdk version "11.0.26" 2025-01-21
OpenJDK Runtime Environment (build 11.0.26+4-post-Ubuntu-1ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.26+4-post-Ubuntu-1ubuntu122.04, mixed mode, sharing)
which java # where is it running?
/usr/bin/java
# maven was not installed so installed it
sudo apt install maven
mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 11.0.26, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-52-generic", arch: "amd64", family: "unix"
# then I created a directory
mkdir -p ~/projects/java/
cd ~/projects/java
# and generated with maven a project called "webcam-test"
mvn archetype:generate -DgroupId=com.example -DartifactId=webcam-test -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
# which generated a folder with the structure
webcam-test
├── firstCapture.JPG
├── pom.xml
├── src/main/java/com/example/App.java
# nano pom.xml
# open pom.xml and add inside <dependencies></dependencies> this section:
<dependencies>
<dependency>
<groupId>com.github.sarxos</groupId>
<artifactId>webcam-capture</artifactId>
<version>0.3.12</version>
</dependency>
</dependencies>
# and at the same level like <dependencies> add a section
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
# this avoids an error otherwise occurring - since java -version gave 11 as version
# it was important to ensure that 11 is used.
# so the full file content pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>webcam-test</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>webcam-test</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.sarxos</groupId>
<artifactId>webcam-capture</artifactId>
<version>0.3.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
# save and close it.
# then:
# nano src/main/java/com/example/App.java
# and enter the content:
package com.example;
import com.github.sarxos.webcam.Webcam;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
public class App {
public static void main(String[] args) {
Webcam webcam = Webcam.getDefault();
if (webcam == null) {
System.out.println("No webcam detected!");
return;
}
webcam.open();
try {
ImageIO.write(webcam.getImage(), "JPG", new File("firstCapture.JPG"));
System.out.println("Image captured successfully!");
} catch (IOException e) {
e.printStackTrace();
} finally {
webcam.close();
}
}
}
# that is the script for ubuntu.
# save and close.
To build and run, from inside the webcam-test
folder, run:
# build
mvn package
# run
mvn exec:java -Dexec.mainClass="com.example.App"
This should capture a picture and save as firstCapture.JPG
.
For Clojure, we want to ensure that Clojure uses the same Java. Therefore we define JAVA_HOME
and add the binary folder into PATH
:
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
To find out your exact path, do:
ls -l /usr/lib/jvm
and check the current running java version with
java -version
For me, this gave:
lrwxrwxrwx 1 root root 25 Mär 24 2022 default-java -> java-1.11.0-openjdk-amd64
lrwxrwxrwx 1 root root 21 Jul 21 2024 java-1.11.0-openjdk-amd64 -> java-11-openjdk-amd64
drwxr-xr-x 7 root root 4096 Mär 11 14:23 java-11-openjdk-amd64
# and:
openjdk version "11.0.26" 2025-01-21
OpenJDK Runtime Environment (build 11.0.26+4-post-Ubuntu-1ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.26+4-post-Ubuntu-1ubuntu122.04, mixed mode, sharing)
Don't do
export JAVA_HOME=$(which java)
I did it initially but that results in problems later. It should be a /usr/lib/jvm
path.
Generate the folder and files:
mkdir -p ~/projects/clojure/clojure-webcam
cd ~/projects/clojure/clojure-webcam
Create deps.edn
: nano deps.edn
{
:paths ["src"]
:deps {
org.clojure/clojure {:mvn/version "1.11.1"}
com.github.sarxos/webcam-capture {:mvn/version "0.3.12"}
}
}
You might need different version numbers in future. Today is March 20 2025
Create then the code folder and file:
mkdir -p src
nano src/webcam.clj
Enter the content:
(ns webcam
(:import [com.github.sarxos.webcam Webcam]
[javax.imageio ImageIO]
[java.io File]))
(defn capture-image []
(let [webcam (Webcam/getDefault)]
(if (nil? webcam)
(do
(println "No webcam found!")
(System/exit 1)) ;; Exit with error code
(try
(.open webcam)
(ImageIO/write (.getImage webcam) "JPG" (File. "clojure-webcam.jpg"))
(println "Image captured!")
(catch Exception e
(println "Error capturing image:" (.getMessage e)))
(finally
(when (.isOpen webcam)
(.close webcam))))))) ;; Ensure webcam closes
(defn -main []
(capture-image)
(shutdown-agents) ;; Prevent lingering threads
(System/exit 0)) ;; Exit gracefully
The try - catch - finally was necessary because some error occured while closing.
Alternatively use with-open
(ns webcam
(:import [com.github.sarxos.webcam Webcam]
[javax.imageio ImageIO]
[java.io File Closeable]))
(defn capture-image []
(let [webcam (Webcam/getDefault)]
(if (nil? webcam)
(do
(println "No webcam found!")
(System/exit 1))
(with-open [w webcam]
(.open w)
(ImageIO/write (.getImage w) "JPG" (File. "clojure-webcam.jpg"))
(println "Image captured!")))))
(defn -main []
(capture-image)
(shutdown-agents)
(System/exit 0))
You can test first with
(let [webcam (Webcam/getDefault)]
(println "Implements Closeable? " (instance? java.io.Closeable webcam))
(println "Implements AutoCloseable? " (instance? java.lang.AutoCloseable webcam)))
Whether this would work. I didn't test the with-clojure expression.
Run the clojure program by:
clojure -M -m webcam
Without the finally clause, I had this error occuring:
:~/projects/clj/clojure-webcam$ clojure -M -m webcam
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Image captured!
Execution error (NullPointerException) at clojure.main/main (main.java:40).
null
Full report at:
/tmp/clojure-18393721443446853860.edn
And the report's content was:
GNU nano 6.2 /tmp/clojure-18393721443446853860.edn
{:clojure.main/message
"Execution error (NullPointerException) at clojure.main/main (main.java:40).\nnull\n",
:clojure.main/triage
{:clojure.error/class java.lang.NullPointerException,
:clojure.error/line 40,
:clojure.error/symbol clojure.main/main,
:clojure.error/source "main.java",
:clojure.error/phase :execution},
:clojure.main/trace
{:via
[{:type java.lang.NullPointerException,
:at [clojure.core$apply invokeStatic "core.clj" 667]}],
:trace
[[clojure.core$apply invokeStatic "core.clj" 667]
[clojure.main$main_opt invokeStatic "main.clj" 514]
[clojure.main$main_opt invoke "main.clj" 510]
[clojure.main$main invokeStatic "main.clj" 664]
[clojure.main$main doInvoke "main.clj" 616]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.main main "main.java" 40]]}}
And the original code with this error was:
(ns webcam
(:import [com.github.sarxos.webcam Webcam]
[javax.imageio ImageIO]
[java.io File]))
(defn capture-image []
(let [webcam (Webcam/getDefault)]
(if (nil? webcam)
(println "No webcam found!")
(do
(.open webcam)
(ImageIO/write (.getImage webcam) "JPG" (File. "clojure-webcam.jpg"))
(println "Image captured!")
(.close webcam)))))
(capture-image)
I didn't figured out what the error was exactly - maybe someone has an idea?
Anyhow that's it - this works in ubuntu.