Current section

Files

Jump to
ex_bardecode src bardecode.c
Raw

src/bardecode.c

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <jpeglib.h>
#include <setjmp.h>
#include "erl_nif.h"
#include "barcode.h"
#include "base64.h"
#define MAX_ATOM_LENGTH 256
// get_string gets an Elixir string (binary)
// the caller is responsible for freeing the returned pointer.
static char *get_string(ErlNifEnv *env, ERL_NIF_TERM term) {
ErlNifBinary bin;
if (!enif_inspect_iolist_as_binary(env, term, &bin)) {
return NULL;
}
char *str = strndup((char*) bin.data, bin.size);
enif_release_binary(&bin);
return str;
}
// make_string makes an Elixir string (binary)
static ERL_NIF_TERM make_string(ErlNifEnv *env, const char *str) {
ERL_NIF_TERM result;
size_t size = strlen(str);
unsigned char *chars = enif_make_new_binary(env, size, &result);
strncpy((char *)chars, str, size);
return result;
}
// has_prefix checks if a string begins with the prefix string
static bool has_prefix(const char *s, const char *prefix) {
if(strncmp(s, prefix, strlen(prefix)) == 0) {
return true;
}
return false;
}
// Create barcode session with appropriate parameters.
// Caller is responsible for calling STFreeBarCodeSession when done.
static void *_create_session(ErlNifEnv *env, ERL_NIF_TERM options) {
bool ok;
if (!enif_is_map(env, options)) {
return NULL;
}
ERL_NIF_TERM atom_true = enif_make_atom(env, "true");
// %{multiple: true} waits for multiple barcodes instead of just the first one.
uint16 multiple = 0;
ERL_NIF_TERM multiple_term;
ok = enif_get_map_value(env, options, enif_make_atom(env, "multiple"), &multiple_term);
if(ok && enif_is_atom(env, multiple_term) && enif_compare(multiple_term, atom_true) == 0) {
multiple = 1;
}
// %{max_threads: 2} sets the number of threads, default (0) based on number of cpu cores.
uint16 max_threads = 0;
int value = 0;
ERL_NIF_TERM max_threads_term;
ok = enif_get_map_value(env, options, enif_make_atom(env, "max_threads"), &max_threads_term);
if (ok && enif_get_int(env, max_threads_term, &value)) {
max_threads = value;
}
// %{timeout: 5000} maximum time in milliseconds, default is 5000.
uint16 timeout = 5000;
ERL_NIF_TERM timeout_term;
ok = enif_get_map_value(env, options, enif_make_atom(env, "timeout"), &timeout_term);
if (ok && enif_get_int(env, timeout_term, &value)) {
timeout = value;
}
// barcode types: %{type: [:pdf417, :code128]}
uint16 codabar = 0, code39 = 0, code128 = 0, code25 = 0, ean13 = 0;
uint16 ean8 = 0, upca = 0, upce = 0, pdf417 = 0, datamatrix = 0;
ERL_NIF_TERM type_term;
ok = enif_get_map_value(env, options, enif_make_atom(env, "type"), &type_term);
if(ok) {
if(enif_is_atom(env, type_term)) {
// TODO: could support a single atom,
// or support binary strings in addition to atoms
}
if(enif_is_list(env, type_term)) {
ERL_NIF_TERM head, tail, list = type_term;
char type[MAX_ATOM_LENGTH];
while(enif_get_list_cell(env, list, &head, &tail)) {
if(enif_is_atom(env, head)) {
ok = enif_get_atom(env, head, type, MAX_ATOM_LENGTH, ERL_NIF_LATIN1);
if(ok) {
if (strcmp(type, "codabar") == 0) {
codabar = 1;
} else if (strcmp(type, "code39") == 0) {
code39 = 1;
} else if (strcmp(type, "code128") == 0) {
code128 = 1;
} else if (strcmp(type, "code25") == 0) {
code25 = 1;
} else if (strcmp(type, "ean13") == 0) {
ean13 = 1;
} else if (strcmp(type, "ean8") == 0) {
ean8 = 1;
} else if (strcmp(type, "upca") == 0) {
upca = 1;
} else if (strcmp(type, "upce") == 0) {
upce = 1;
} else if (strcmp(type, "pdf417") == 0) {
pdf417 = 1;
} else if (strcmp(type, "datamatrix") == 0) {
datamatrix = 1;
} else {
return NULL; // unrecognized type
}
} else {
return NULL; // atom is too long
}
} else {
return NULL; // not an atom
}
list = tail;
}
}
} else {
// if not specified, default to any type
codabar = code39 = code128 = code25 = ean13 = ean8 = upca = upce = pdf417 = datamatrix = 1;
}
// license key: %{license: "..."}
char *license = NULL;
ERL_NIF_TERM license_term;
ok = enif_get_map_value(env, options, enif_make_atom(env, "license"), &license_term);
if (ok) {
license = get_string(env, license_term);
if (!license) {
return NULL;
}
}
// create bardecode session
void *hBarcode = STCreateBarCodeSession();
STSetParameter(hBarcode, ST_MULTIPLE_READ, &multiple);
STSetParameter(hBarcode, ST_MAX_THREADS, &max_threads);
STSetParameter(hBarcode, ST_TIMEOUT, &timeout);
STSetParameter(hBarcode, ST_READ_CODABAR, &codabar);
STSetParameter(hBarcode, ST_READ_CODE39, &code39);
STSetParameter(hBarcode, ST_READ_CODE128, &code128);
STSetParameter(hBarcode, ST_READ_CODE25, &code25);
STSetParameter(hBarcode, ST_READ_EAN13, &ean13);
STSetParameter(hBarcode, ST_READ_EAN8, &ean8);
STSetParameter(hBarcode, ST_READ_UPCA, &upca);
STSetParameter(hBarcode, ST_READ_UPCE, &upce);
STSetParameter(hBarcode, ST_READ_PDF417, &pdf417);
STSetParameter(hBarcode, ST_READ_DATAMATRIX, &datamatrix);
if (license) {
STSetParameter(hBarcode, ST_LICENSE, license);
}
free(license);
return hBarcode;
}
// _translate_error codes from bardecode into an {:error, "message"} tuple.
static ERL_NIF_TERM _translate_error(ErlNifEnv *env, int error) {
ERL_NIF_TERM message;
switch(error) {
case ST_ERROR_FILE_OPEN:
message = make_string(env, "cannot open image file");
break;
default:
message = make_string(env, "error reading image file");
}
return enif_make_tuple2(env, enif_make_atom(env, "error"), message);
}
// _prepare_result translates barcode results into a list of maps and returns an ok tuple or an error.
static ERL_NIF_TERM _prepare_result(ErlNifEnv *env, int bar_count, char **bar_codes_value, char **bar_codes_type) {
if (bar_count < 0) {
return _translate_error(env, bar_count);
}
// make an empty list to populate
ERL_NIF_TERM barcodes = enif_make_list(env, 0);
// make :kind and :value atoms as keys for the maps
ERL_NIF_TERM atom_kind = enif_make_atom(env, "kind");
ERL_NIF_TERM atom_value = enif_make_atom(env, "rawValue");
// make a map for each barcode and append to the list.
for (int n = 0; n < bar_count; n++) {
ERL_NIF_TERM barcode = enif_make_new_map(env);
enif_make_map_put(env, barcode, atom_kind, make_string(env, bar_codes_type[n]), &barcode);
enif_make_map_put(env, barcode, atom_value, make_string(env, bar_codes_value[n]), &barcode);
barcodes = enif_make_list_cell(env, barcode, barcodes);
}
return enif_make_tuple2(env, enif_make_atom(env, "ok"), barcodes);
}
// my_error_mgr for JPEG decoding error recovery
struct my_error_mgr {
struct jpeg_error_mgr pub; // "public" fields
jmp_buf setjmp_buffer; // for return to caller
};
typedef struct my_error_mgr *my_error_ptr;
void my_error_exit(j_common_ptr cinfo)
{
// cinfo->err really points to a my_error_mgr struct, so coerce pointer
my_error_ptr myerr = (my_error_ptr) cinfo->err;
// Return control to the setjmp point
longjmp(myerr->setjmp_buffer, 1);
}
// _decode_jpeg_from_memory
static ERL_NIF_TERM _decode_jpeg_from_memory(ErlNifEnv *env, ERL_NIF_TERM options, unsigned char *bitmap_data, unsigned bitmap_bytes) {
unsigned char *bmp_buffer = NULL;
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
// Establish the setjmp return context for my_error_exit to use.
if (setjmp(jerr.setjmp_buffer)) {
// If we get here, the JPEG code has signaled an error.
char message[JMSG_LENGTH_MAX];
cinfo.err->format_message((j_common_ptr)&cinfo, message);
jpeg_destroy_decompress(&cinfo);
if (bmp_buffer != NULL) {
free(bmp_buffer);
}
return enif_make_tuple2(env, enif_make_atom(env, "error"), make_string(env, message));
}
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, bitmap_data, bitmap_bytes);
if (jpeg_read_header(&cinfo, TRUE) != 1) {
return enif_make_tuple2(env, enif_make_atom(env, "error"), make_string(env, "error reading JPEG header"));
}
jpeg_start_decompress(&cinfo);
uint16_t width = cinfo.output_width;
uint16_t height = cinfo.output_height;
int row_stride = width * cinfo.output_components;
// Make a one-row-high sample array that will go away when done with the image.
JSAMPARRAY scanline_buffer = (*cinfo.mem->alloc_sarray)
((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
// NOTE: bmp_buffer scan lines in the image must begin on 4 byte boundary
uint16_t padded_width = (width + 3) / 4 * 4;
unsigned long bmp_size = padded_width * height;
bmp_buffer = (unsigned char*) malloc(bmp_size);
uint16_t row = 0, col = 0;
uint8_t r, g, b, lum;
while (cinfo.output_scanline < cinfo.output_height) {
if (jpeg_read_scanlines(&cinfo, scanline_buffer, 1) == 1) {
for(col = 0; col < width; col++) {
r = scanline_buffer[0][3 * col];
g = scanline_buffer[0][3 * col + 1];
b = scanline_buffer[0][3 * col + 2];
lum = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
bmp_buffer[row * padded_width + col] = lum;
}
row++;
}
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
BITMAP bm;
bm.bmType = 1;
bm.bmWidth = width; // width of image in pixels
bm.bmHeight = height;
bm.bmWidthBytes = padded_width; // width of scan line, padded to 4-byte boundary
bm.bmBitsPixel = 8;
bm.bmPlanes = 1;
bm.bmBits = bmp_buffer;
float dpi = 200; // TODO: does it matter?
// create bar code session with appropriate parameters.
void *hBarcode = _create_session(env, options);
if(!hBarcode) {
free(bmp_buffer);
return enif_make_badarg(env);
}
// read bar codes
char **bar_codes_value;
char **bar_codes_type;
int bar_count = STReadBarCodeFromBitmap(hBarcode, &bm, dpi, &bar_codes_value, &bar_codes_type, 0);
ERL_NIF_TERM result = _prepare_result(env, bar_count, bar_codes_value, bar_codes_type);
// clean up
STFreeBarCodeSession(hBarcode);
free(bmp_buffer);
return result;
}
// read_barcode from a file on disk (JPG/TIFF).
static ERL_NIF_TERM read_barcode(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
char *file = get_string(env, argv[0]);
if (!file) {
return enif_make_badarg(env);
}
ERL_NIF_TERM options = argv[1];
// create bar code session with appropriate parameters.
void *hBarcode = _create_session(env, options);
if(!hBarcode) {
free(file);
return enif_make_badarg(env);
}
// read bar codes
char **bar_codes_value;
char **bar_codes_type;
int bar_count = STReadBarCode(hBarcode, file, 0, &bar_codes_value, &bar_codes_type);
ERL_NIF_TERM result = _prepare_result(env, bar_count, bar_codes_value, bar_codes_type);
// clean up
STFreeBarCodeSession(hBarcode);
free(file);
return result;
}
// read_barcode_from_bitmap from memory (JPEG format).
static ERL_NIF_TERM read_barcode_from_bitmap(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
ErlNifBinary bitmap;
if (!enif_inspect_iolist_as_binary(env, argv[0], &bitmap)) {
return enif_make_badarg(env);
}
ERL_NIF_TERM options = argv[1];
ERL_NIF_TERM result = _decode_jpeg_from_memory(env, options, bitmap.data, bitmap.size);
enif_release_binary(&bitmap);
return result;
}
#define DATA_URL_PREFIX "data:image/jpeg;base64,"
// read_barcode_from_data_url from memory (base64 JPEG format).
static ERL_NIF_TERM read_barcode_from_data_url(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
char *data_url = get_string(env, argv[0]);
if (!data_url) {
return enif_make_badarg(env);
}
if (!has_prefix(data_url, DATA_URL_PREFIX)) {
free(data_url);
return enif_make_tuple2(env, enif_make_atom(env, "error"), make_string(env, "expected image/jpeg data URL"));
}
ERL_NIF_TERM options = argv[1];
int bitmap_size;
char *encoded_data = &data_url[strlen(DATA_URL_PREFIX)];
unsigned char *bitmap_data = unbase64(encoded_data, strlen(encoded_data), &bitmap_size);
if(bitmap_size == 0) {
free(data_url);
return enif_make_tuple2(env, enif_make_atom(env, "error"), make_string(env, "invalid base64 string"));
}
ERL_NIF_TERM result = _decode_jpeg_from_memory(env, options, bitmap_data, bitmap_size);
free(bitmap_data);
free(data_url);
return result;
}
// ErlNifFunc array exposes native implemented functions to Elixir with dirty scheduler.
static ErlNifFunc nif_funcs[] = {
// {erl_function_name, erl_function_arity, c_function}
{"read_barcode", 2, read_barcode, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"read_barcode_from_bitmap", 2, read_barcode_from_bitmap, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"read_barcode_from_data_url", 2, read_barcode_from_data_url, ERL_NIF_DIRTY_JOB_CPU_BOUND}
};
ERL_NIF_INIT(Elixir.Bardecode, nif_funcs, NULL, NULL, NULL, NULL)