turbojpeg provides Scala Native bindings for the TurboJPEG API of libjpeg-turbo — the SIMD-accelerated JPEG codec used throughout the industry. It decodes JPEG images into packed-pixel buffers, including straight into a buffer you already own (an image surface, say) with no intermediate copy.
libjpeg-turbo ships two C APIs: the classic Independent JPEG Group libjpeg API and the newer,
higher-level TurboJPEG API. This binding wraps TurboJPEG, which is the clean fit for FFI — an
opaque instance handle instead of a caller-allocated decompress struct, and integer return codes
instead of the classic API's setjmp/longjmp error model.
The programmer-friendly facade lives in the io.github.edadma.turbojpeg package — that is the only
package you need to import. The io.github.edadma.turbojpeg.extern package holds the raw C interop
using Scala Native's unsafe types; the facade exposes those only in the one zero-copy overload
(Decoder.decompress, which writes into a Ptr[Byte] you supply), so ordinary use never touches
manual memory management.
This binding currently covers decompression; compression and the legacy libjpeg API are out
of scope.
This library is published for Scala Native to Maven Central. libjpeg-turbo must be installed on your system:
brew install jpeg-turbo # macOS (Homebrew)
sudo apt install libturbojpeg0-dev # Debian / UbuntuEnable Scala Native in project/plugins.sbt:
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.12")Add the dependency in build.sbt:
libraryDependencies += "io.github.edadma" %%% "turbojpeg" % "0.0.1"Then import the facade:
import io.github.edadma.turbojpeg.*Decode a JPEG into an RGBA array:
import io.github.edadma.turbojpeg.*
val bytes: Array[Byte] = ??? // the contents of a .jpg
val img = decode(bytes, PixelFormat.RGBA)
println(s"${img.width}x${img.height}, ${img.pixels.length} bytes")Or read just the header, then decode directly into a buffer you already own — no extra copy.
Because the BGRA format fills the alpha byte with 0xFF and JPEG has no transparency, this lands
exactly as opaque 32-bit pixels (e.g. straight into an image surface):
import io.github.edadma.turbojpeg.*
import scala.scalanative.unsafe.*
val dec = Decoder()
val info = dec.readHeader(bytes) // width / height / precision, no decompression
// allocate `dst` of at least info.height * pitch bytes (e.g. a surface's pixel buffer)
val dst: Ptr[Byte] = ???
val pitch = ??? // bytes per row of dst (0 = tightly packed)
dec.decompress(bytes, dst, pitch, PixelFormat.BGRA)
dec.close()Top-level helpers (each manages a handle for you; both throw TurboJpegException on a bad image):
info(jpeg: Array[Byte]): ImageInfo— read the header (width,height,precision) without decompressingdecode(jpeg: Array[Byte], format: PixelFormat = PixelFormat.RGBA): Image— decode into a fresh packed-pixel array
For reuse or zero-copy decoding, a Decoder (a reusable instance handle; not thread-safe):
Decoder(): DecoderreadHeader(jpeg: Array[Byte]): ImageInfodecompress(jpeg: Array[Byte], dst: Ptr[Byte], pitch: Int, format: PixelFormat): Unit— decode into a caller-supplied buffer (pitch= bytes per row,0for tightly packed)close(): Unit— release the handle
PixelFormat values: RGB, BGR, RGBX, BGRX, XBGR, XRGB, Gray, RGBA, BGRA, ABGR,
ARGB, CMYK (with .bytesPerPixel).
API documentation is forthcoming; documentation for the C library is found here.