Current section
Files
Jump to
Current section
Files
src/bardecode.c
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <jpeglib.h>
#include "erl_nif.h"
#include "barcode.h"
#include "base64.h"
// Create barcode session with appropriate parameters.
// Caller is responsible for calling STFreeBarCodeSession when done.
static void *_create_session(char *license) {
void *hBarcode = STCreateBarCodeSession();
uint16 ival = 1;
STSetParameter(hBarcode, ST_MULTIPLE_READ, &ival);
STSetParameter(hBarcode, ST_READ_PDF417, &ival);
STSetParameter(hBarcode, ST_READ_CODE128, &ival);
STSetParameter(hBarcode, ST_LICENSE, license);
return hBarcode;
}
// 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;
}
static bool has_prefix(const char *s, const char *prefix) {
if(strncmp(s, prefix, strlen(prefix)) == 0) {
return true;
}
return false;
}
// _translate_error codes 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);
}
// 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);
}
char *license = get_string(env, argv[1]);
if (!license) {
return enif_make_badarg(env);
}
// create bar code session with appropriate parameters.
void *hBarcode = _create_session(license);
// 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);
free(license);
return result;
}
// _decode_jpeg_from_memory
static ERL_NIF_TERM _decode_jpeg_from_memory(ErlNifEnv *env, char *license, unsigned char *bitmap_data, unsigned bitmap_bytes) {
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
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;
uint8_t pixel_size = cinfo.output_components;
uint32_t scanline_size = width * pixel_size;
unsigned char *scanline_buffer = (unsigned char*) malloc(scanline_size);
// 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;
unsigned char *bmp_buffer = (unsigned char*) malloc(bmp_size);
uint16_t row = 0, col = 0;
uint8_t r, g, b, lum;
while (row < height) {
if (jpeg_read_scanlines(&cinfo, &scanline_buffer, 1) == 1) {
for(col = 0; col < width; col++) {
r = scanline_buffer[3 * col];
g = scanline_buffer[3 * col + 1];
b = scanline_buffer[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(license);
// 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(scanline_buffer);
free(bmp_buffer);
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);
}
char *license = get_string(env, argv[1]);
if (!license) {
return enif_make_badarg(env);
}
ERL_NIF_TERM result = _decode_jpeg_from_memory(env, license, bitmap.data, bitmap.size);
free(license);
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"));
}
char *license = get_string(env, argv[1]);
if (!license) {
free(data_url);
return enif_make_badarg(env);
}
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);
free(license);
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, license, bitmap_data, bitmap_size);
free(bitmap_data);
free(license);
free(data_url);
return result;
}
// Let's define the array of ErlNifFunc beforehand:
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)