edadma / turbojpeg   0.0.1

ISC License GitHub
Scala versions: 3.x
Scala Native versions: 0.5

turbojpeg

Maven Central Last Commit License Scala Version Scala Native Version

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.

Overview

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.

Usage

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 / Ubuntu

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

Example

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()

API

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 decompressing
  • decode(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(): Decoder
  • readHeader(jpeg: Array[Byte]): ImageInfo
  • decompress(jpeg: Array[Byte], dst: Ptr[Byte], pitch: Int, format: PixelFormat): Unit — decode into a caller-supplied buffer (pitch = bytes per row, 0 for tightly packed)
  • close(): Unit — release the handle

PixelFormat values: RGB, BGR, RGBX, BGRX, XBGR, XRGB, Gray, RGBA, BGRA, ABGR, ARGB, CMYK (with .bytesPerPixel).

Documentation

API documentation is forthcoming; documentation for the C library is found here.

License

ISC