Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ package lilliput
#cgo darwin CXXFLAGS: -I${SRCDIR}/deps/osx/include -I${SRCDIR}/deps/osx/include/opencv4
#cgo linux,amd64 CXXFLAGS: -I${SRCDIR}/deps/linux/amd64/include -I${SRCDIR}/deps/linux/amd64/include/opencv4
#cgo linux,arm64 CXXFLAGS: -I${SRCDIR}/deps/linux/aarch64/include -I${SRCDIR}/deps/linux/aarch64/include/opencv4
#cgo darwin LDFLAGS: -L${SRCDIR}/deps/osx/lib -L${SRCDIR}/deps/osx/lib/opencv4/3rdparty -lavif -lyuv -laom -ldav1d -lavformat -lavcodec -lavutil -lopencv_photo -lopencv_imgcodecs -lopencv_imgproc -lopencv_core -lbz2 -lgif -ljpeg -lpng -lswscale -lwebp -lwebpmux -lwebpdemux -lsharpyuv -lz -llibopenjp2 -littnotify -framework Accelerate -framework CoreFoundation -framework CoreMedia -framework CoreVideo -framework VideoToolbox -llcms2
#cgo linux,amd64 LDFLAGS: -L${SRCDIR}/deps/linux/amd64/lib -L${SRCDIR}/deps/linux/amd64/lib/opencv4/3rdparty -lavif -lyuv -laom -ldav1d -lavformat -lavcodec -lavutil -lopencv_photo -lopencv_imgcodecs -lopencv_imgproc -lopencv_core -lbz2 -lgif -ljpeg -lpng16 -lswscale -lwebp -lwebpmux -lwebpdemux -lsharpyuv -lz -llibopenjp2 -littnotify -lippiw -lippicv -llcms2
#cgo linux,arm64 LDFLAGS: -L${SRCDIR}/deps/linux/aarch64/lib -L${SRCDIR}/deps/linux/aarch64/lib/opencv4/3rdparty -lavif -lyuv -laom -ldav1d -lavformat -lavcodec -lavutil -lopencv_photo -lopencv_imgcodecs -lopencv_imgproc -lopencv_core -lbz2 -lgif -ljpeg -lpng16 -lswscale -lwebp -lwebpmux -lwebpdemux -lsharpyuv -lz -llibopenjp2 -littnotify -llcms2
#cgo darwin LDFLAGS: -L${SRCDIR}/deps/osx/lib -L${SRCDIR}/deps/osx/lib/opencv4/3rdparty -lavif -lyuv -laom -ldav1d -lavformat -lavcodec -lavutil -lopencv_photo -lopencv_imgcodecs -lopencv_imgproc -lopencv_core -lbz2 -lgif -lturbojpeg -ljpeg -lpng -lswscale -lwebp -lwebpmux -lwebpdemux -lsharpyuv -lz -llibopenjp2 -littnotify -framework Accelerate -framework CoreFoundation -framework CoreMedia -framework CoreVideo -framework VideoToolbox -llcms2
#cgo linux,amd64 LDFLAGS: -L${SRCDIR}/deps/linux/amd64/lib -L${SRCDIR}/deps/linux/amd64/lib/opencv4/3rdparty -lavif -lyuv -laom -ldav1d -lavformat -lavcodec -lavutil -lopencv_photo -lopencv_imgcodecs -lopencv_imgproc -lopencv_core -lbz2 -lgif -lturbojpeg -ljpeg -lpng16 -lswscale -lwebp -lwebpmux -lwebpdemux -lsharpyuv -lz -llibopenjp2 -littnotify -lippiw -lippicv -llcms2
#cgo linux,arm64 LDFLAGS: -L${SRCDIR}/deps/linux/aarch64/lib -L${SRCDIR}/deps/linux/aarch64/lib/opencv4/3rdparty -lavif -lyuv -laom -ldav1d -lavformat -lavcodec -lavutil -lopencv_photo -lopencv_imgcodecs -lopencv_imgproc -lopencv_core -lbz2 -lgif -lturbojpeg -ljpeg -lpng16 -lswscale -lwebp -lwebpmux -lwebpdemux -lsharpyuv -lz -llibopenjp2 -littnotify -llcms2
void dummy() {}
*/
import "C"
Expand Down
168 changes: 168 additions & 0 deletions jpeg.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#include "jpeg.hpp"

#include <cstdint>
#include <turbojpeg.h>
#include <cstdio>
#include <jpeglib.h>
#include <jerror.h>
#include <cstring>
#include <vector>
#include <span>

struct jpeg_encoder_struct {
uint8_t* dst{};
size_t dst_len{};
std::span<const unsigned char> icc_profile{};
};

jpeg_encoder jpeg_encoder_create(void* dst,
const size_t dst_len,
const void* icc_data,
const size_t icc_len)
{
const auto impl = new jpeg_encoder_struct{
static_cast<uint8_t*>(dst), dst_len
};

if (icc_data && icc_len > 0) {
impl->icc_profile = {
static_cast<const unsigned char*>(icc_data), icc_len
};
}

return impl;
}

void jpeg_encoder_release(jpeg_encoder e)
{
delete e;
}

// Helper function to get the canonical JPEG buffer error message from jerror.h
static const char* get_jpeg_buffer_error_message()
{
jpeg_error_mgr jerr{};
jpeg_std_error(&jerr);
return jerr.jpeg_message_table[JERR_BUFFER_SIZE];
}

int jpeg_encoder_encode(jpeg_encoder e,
const void* src_data,
const int width,
const int height,
const int channels,
const size_t stride,
const int* opt,
const size_t opt_len,
size_t* out_size)
{
// Validate input parameters
// Check dimensions first for better error specificity
if (width == 0 || height == 0 || stride == 0) {
return L_JPEG_ERROR_INVALID_DIMENSIONS;
}
if (!e || !src_data || (!opt && opt_len > 0) || !out_size) {
return L_JPEG_ERROR_INVALID_ARG;
}
if (!e->dst) {
return L_JPEG_ERROR_NULL_MATRIX;
}

// Extract encoding parameters
int quality = L_JPEG_DEFAULT_QUALITY;
int progressive = L_JPEG_DEFAULT_PROGRESSIVE;
if (opt) {
for (size_t i = 0; i + 1 < opt_len; i += 2) {
if (opt[i] == L_JPEG_QUALITY) {
quality = opt[i + 1];
} else if (opt[i] == L_JPEG_PROGRESSIVE) {
progressive = opt[i + 1];
}
}
}

// Clamp quality to valid range [1, 100]
if (quality <= 0) {
quality = 1;
} else if (quality > 100) {
quality = 100;
}

// Determine pixel format based on channel count
// TurboJPEG supports encoding from grayscale, BGR, and BGRA directly
int pixelFormat;
int subsampling;
if (channels == 1) {
pixelFormat = TJPF_GRAY;
subsampling = TJSAMP_GRAY;
} else if (channels == 3) {
pixelFormat = TJPF_BGR;
subsampling = TJSAMP_420;
} else if (channels == 4) {
pixelFormat = TJPF_BGRA; // Alpha will be discarded
subsampling = TJSAMP_420;
} else {
return L_JPEG_ERROR_INVALID_CHANNEL_COUNT;
}

// Set up TurboJPEG compressor
tjhandle handle = tj3Init(TJINIT_COMPRESS);
if (!handle) {
return L_JPEG_ERROR_UNKNOWN;
}

// Configure compression parameters
tj3Set(handle, TJPARAM_QUALITY, quality);
tj3Set(handle, TJPARAM_OPTIMIZE, L_JPEG_DEFAULT_OPTIMIZE);
tj3Set(handle, TJPARAM_SUBSAMP, subsampling);
tj3Set(handle, TJPARAM_PROGRESSIVE, progressive);
tj3Set(handle, TJPARAM_NOREALLOC, 1); // Disable buffer reallocation

// Set ICC profile if present
if (!e->icc_profile.empty()) {
tj3SetICCProfile(
handle,
const_cast<unsigned char*>(e->icc_profile.data()),
e->icc_profile.size()
);
}

// Compress image
unsigned char* buffer = e->dst;
size_t bufferSize = e->dst_len;
const int result = tj3Compress8(
handle,
static_cast<const unsigned char*>(src_data),
width,
stride,
height,
pixelFormat,
&buffer,
&bufferSize
);

if (result == -1) {
*out_size = 0;
const char* errorStr = tj3GetErrorStr(handle);
tj3Destroy(handle);

if (errorStr) {
// There doesn't seem to be a way to get the error code directly,
// so check against jerror.h message corresponding to the buffer
// being too small
const char* bufferErrMsg = get_jpeg_buffer_error_message();
if (bufferErrMsg && std::strstr(errorStr, bufferErrMsg)) {
return L_JPEG_ERROR_BUFFER_TOO_SMALL;
}
}

// The same strategy could be used to check for other specific
// errors, but none of them seem too interesting at the moment
return L_JPEG_ERROR_UNKNOWN;
}

// Success path
tj3Destroy(handle);
*out_size = bufferSize;
return L_JPEG_SUCCESS;
}
115 changes: 115 additions & 0 deletions jpeg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package lilliput

// #include "jpeg.hpp"
// #include "opencv.hpp"
import "C"

import (
"io"
"unsafe"
)

// jpegEncoder implements the Encoder interface for JPEG images.
type jpegEncoder struct {
encoder C.jpeg_encoder
dstBuf []byte
}

// newJpegEncoder creates a new JPEG encoder using the provided decoder for metadata
// and destination buffer for the encoded output.
func newJpegEncoder(decodedBy Decoder, dstBuf []byte) (*jpegEncoder, error) {
dstBuf = dstBuf[:1]

// Get ICC profile if available
var icc []byte
if decodedBy != nil {
icc = decodedBy.ICC()
}

var enc C.jpeg_encoder
if len(icc) > 0 {
enc = C.jpeg_encoder_create(unsafe.Pointer(&dstBuf[0]), C.size_t(cap(dstBuf)),
unsafe.Pointer(&icc[0]), C.size_t(len(icc)))
} else {
enc = C.jpeg_encoder_create(unsafe.Pointer(&dstBuf[0]), C.size_t(cap(dstBuf)), nil, 0)
}

if enc == nil {
return nil, ErrInvalidImage
}

return &jpegEncoder{
encoder: enc,
dstBuf: dstBuf,
}, nil
}

// Encode encodes a frame into JPEG format.
// Returns the encoded data or an error.
// The opt parameter allows specifying encoding options as key-value pairs.
func (e *jpegEncoder) Encode(f *Framebuffer, opt map[int]int) ([]byte, error) {
if f == nil {
return nil, io.EOF
}

// Convert opt map to C array
var optList []C.int
var firstOpt *C.int
for k, v := range opt {
optList = append(optList, C.int(k))
optList = append(optList, C.int(v))
}
if len(optList) > 0 {
firstOpt = (*C.int)(unsafe.Pointer(&optList[0]))
}

// Get framebuffer info
var width, height C.int
var stride C.size_t
var data unsafe.Pointer
var channels C.int

if f.mat != nil {
width = C.opencv_mat_get_width(f.mat)
height = C.opencv_mat_get_height(f.mat)
stride = C.opencv_mat_get_step(f.mat)
data = C.opencv_mat_get_data(f.mat)
channels = C.int(f.PixelType().Channels())
}
// If f.mat is nil, all values remain zero/nil, and C encoder will return appropriate error

var length C.size_t
result := C.jpeg_encoder_encode(e.encoder, data, width, height, channels, stride,
firstOpt, C.size_t(len(optList)), &length)

if err := handleJpegError(result); err != nil {
return nil, err
}

return e.dstBuf[:length], nil
}

// Close releases all resources associated with the encoder.
func (e *jpegEncoder) Close() {
C.jpeg_encoder_release(e.encoder)
}

// handleJpegError converts JPEG error codes to Go errors.
func handleJpegError(code C.int) error {
switch code {
case C.L_JPEG_SUCCESS:
return nil
case C.L_JPEG_ERROR_BUFFER_TOO_SMALL:
return ErrBufTooSmall
case C.L_JPEG_ERROR_INVALID_DIMENSIONS:
return ErrFrameBufNoPixels
case C.L_JPEG_ERROR_NULL_MATRIX:
return ErrInvalidImage
case C.L_JPEG_ERROR_INVALID_CHANNEL_COUNT:
return ErrInvalidImage
case C.L_JPEG_ERROR_INVALID_ARG:
return ErrInvalidParam
default:
return ErrInvalidImage
}
}
63 changes: 63 additions & 0 deletions jpeg.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#ifndef LILLIPUT_JPEG_HPP
#define LILLIPUT_JPEG_HPP

#include <stddef.h>
#include "opencv.hpp"

#ifdef __cplusplus
extern "C" {
#endif

// JPEG encoding constants
#define L_JPEG_QUALITY CV_IMWRITE_JPEG_QUALITY
#define L_JPEG_PROGRESSIVE CV_IMWRITE_JPEG_PROGRESSIVE

// Encoder handle
typedef struct jpeg_encoder_struct* jpeg_encoder;

// Error codes
#define L_JPEG_SUCCESS 0
#define L_JPEG_ERROR_INVALID_CHANNEL_COUNT 1
#define L_JPEG_ERROR_NULL_MATRIX 2
#define L_JPEG_ERROR_INVALID_DIMENSIONS 3
#define L_JPEG_ERROR_BUFFER_TOO_SMALL 4
#define L_JPEG_ERROR_INVALID_ARG 5
#define L_JPEG_ERROR_UNKNOWN 6

// Encoder defaults
#define L_JPEG_DEFAULT_QUALITY 95
#define L_JPEG_DEFAULT_PROGRESSIVE 0
#define L_JPEG_DEFAULT_OPTIMIZE 0

// Creates a JPEG encoder with the given output buffer and optional ICC profile
jpeg_encoder jpeg_encoder_create(void* dst,
size_t dst_len,
const void* icc_data,
size_t icc_len);

// Encodes image data to JPEG format
// src_data: raw pixel data (BGR or BGRA format)
// width, height: image dimensions
// channels: 3 for BGR, 4 for BGRA
// stride: bytes per row
// opt: encoding options array (pairs of key, value)
// opt_len: length of opt array
// out_size: output parameter for encoded size
int jpeg_encoder_encode(jpeg_encoder e,
const void* src_data,
int width,
int height,
int channels,
size_t stride,
const int* opt,
size_t opt_len,
size_t* out_size);

// Releases encoder resources
void jpeg_encoder_release(jpeg_encoder e);

#ifdef __cplusplus
}
#endif

#endif
Loading