kopia lustrzana https://gitlab.com/sane-project/backends
enable dynamic rasterization for lineart scans
- use emulated lineart mode to apply binarize function pulled from the epjitsu backend (by m. allan noah).merge-requests/1/head
rodzic
0cc0532505
commit
f42cb07d8d
|
@ -2,11 +2,14 @@
|
|||
|
||||
Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
|
||||
Copyright (C) 2004, 2005 Gerhard Jaeger <gerhard@gjaeger.de>
|
||||
Copyright (C) 2004-2009 Stéphane Voltz <stef.dev@free.fr>
|
||||
Copyright (C) 2004-2010 Stéphane Voltz <stef.dev@free.fr>
|
||||
Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
|
||||
Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com>
|
||||
Copyright (C) 2007 Luke <iceyfor@gmail.com>
|
||||
|
||||
Dynamic rasterization code was taken from the epjistsu backend by
|
||||
m. allan noah <kitno455 at gmail dot com>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
|
@ -164,6 +167,12 @@ static const SANE_Range threshold_percentage_range = {
|
|||
SANE_FIX (1) /* quantization */
|
||||
};
|
||||
|
||||
static const SANE_Range threshold_curve_range = {
|
||||
0, /* minimum */
|
||||
127, /* maximum */
|
||||
1 /* quantization */
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* functions calling ASIC specific functions */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
@ -3986,6 +3995,89 @@ genesys_wait_not_moving (Genesys_Device * dev, int mseconds)
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Function to build a lookup table (LUT), often
|
||||
used by scanners to implement brightness/contrast/gamma
|
||||
or by backends to speed binarization/thresholding
|
||||
|
||||
offset and slope inputs are -127 to +127
|
||||
|
||||
slope rotates line around central input/output val,
|
||||
0 makes horizontal line
|
||||
|
||||
pos zero neg
|
||||
. x . . x
|
||||
. x . . x
|
||||
out . x .xxxxxxxxxxx . x
|
||||
. x . . x
|
||||
....x....... ............ .......x....
|
||||
in in in
|
||||
|
||||
offset moves line vertically, and clamps to output range
|
||||
0 keeps the line crossing the center of the table
|
||||
|
||||
high low
|
||||
. xxxxxxxx .
|
||||
. x .
|
||||
out x . x
|
||||
. . x
|
||||
............ xxxxxxxx....
|
||||
in in
|
||||
|
||||
out_min/max provide bounds on output values,
|
||||
useful when building thresholding lut.
|
||||
0 and 255 are good defaults otherwise.
|
||||
*/
|
||||
static SANE_Status
|
||||
load_lut (unsigned char * lut,
|
||||
int in_bits, int out_bits,
|
||||
int out_min, int out_max,
|
||||
int slope, int offset)
|
||||
{
|
||||
SANE_Status ret = SANE_STATUS_GOOD;
|
||||
int i, j;
|
||||
double shift, rise;
|
||||
int max_in_val = (1 << in_bits) - 1;
|
||||
int max_out_val = (1 << out_bits) - 1;
|
||||
unsigned char * lut_p = lut;
|
||||
|
||||
DBG (DBG_proc, "load_lut: start\n");
|
||||
|
||||
/* slope is converted to rise per unit run:
|
||||
* first [-127,127] to [-1,1]
|
||||
* then multiply by PI/2 to convert to radians
|
||||
* then take the tangent (T.O.A)
|
||||
* then multiply by the normal linear slope
|
||||
* because the table may not be square, i.e. 1024x256*/
|
||||
rise = tan((double)slope/127 * M_PI/2) * max_out_val / max_in_val;
|
||||
|
||||
/* line must stay vertically centered, so figure
|
||||
* out vertical offset at central input value */
|
||||
shift = (double)max_out_val/2 - (rise*max_in_val/2);
|
||||
|
||||
/* convert the user offset setting to scale of output
|
||||
* first [-127,127] to [-1,1]
|
||||
* then to [-max_out_val/2,max_out_val/2]*/
|
||||
shift += (double)offset / 127 * max_out_val / 2;
|
||||
|
||||
for(i=0;i<=max_in_val;i++){
|
||||
j = rise*i + shift;
|
||||
|
||||
if(j<out_min){
|
||||
j=out_min;
|
||||
}
|
||||
else if(j>out_max){
|
||||
j=out_max;
|
||||
}
|
||||
|
||||
*lut_p=j;
|
||||
lut_p++;
|
||||
}
|
||||
|
||||
DBG (DBG_proc, "load_lut: finish\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* High level (exported) functions */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
@ -4267,6 +4359,19 @@ genesys_start_scan (Genesys_Device * dev)
|
|||
return status;
|
||||
}
|
||||
|
||||
/* build look up table for dynamic lineart */
|
||||
if(dev->settings.dynamic_lineart==SANE_TRUE)
|
||||
{
|
||||
status = load_lut(dev->lineart_lut, 8, 8, 50, 205,
|
||||
dev->settings.threshold_curve,
|
||||
dev->settings.threshold-127);
|
||||
if (status != SANE_STATUS_GOOD)
|
||||
{
|
||||
DBG (DBG_error, "genesys_start_scan: failed to build lut\n");
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
status = dev->model->cmd_set->init_regs_for_scan (dev);
|
||||
if (status != SANE_STATUS_GOOD)
|
||||
{
|
||||
|
@ -5010,10 +5115,12 @@ Problems with the first approach:
|
|||
|
||||
bytes = dst_lines * dev->settings.pixels * channels;
|
||||
|
||||
status = genesys_gray_lineart (work_buffer_src, destination,
|
||||
status = genesys_gray_lineart (dev,
|
||||
work_buffer_src,
|
||||
destination,
|
||||
dev->settings.pixels,
|
||||
channels,
|
||||
dst_lines, dev->settings.threshold);
|
||||
dst_lines,
|
||||
dev->settings.threshold);
|
||||
if (status != SANE_STATUS_GOOD)
|
||||
{
|
||||
DBG (DBG_error,
|
||||
|
@ -5173,7 +5280,12 @@ calc_parameters (Genesys_Scanner * s)
|
|||
/* dynamic lineart */
|
||||
s->dev->settings.dynamic_lineart =
|
||||
s->val[OPT_DYNAMIC_LINEART].w == SANE_TRUE;
|
||||
DBG (DBG_io2, "dynamic_lineart=%d\n",s->dev->settings.dynamic_lineart);
|
||||
|
||||
/* threshold curve for dynamic ratserization */
|
||||
if(s->dev->settings.dynamic_lineart==SANE_TRUE)
|
||||
s->dev->settings.threshold_curve=s->val[OPT_THRESHOLD_CURVE].w;
|
||||
else
|
||||
s->dev->settings.threshold_curve=0;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
@ -5446,6 +5558,16 @@ init_options (Genesys_Scanner * s)
|
|||
s->opt[OPT_THRESHOLD].constraint.range = &threshold_percentage_range;
|
||||
s->val[OPT_THRESHOLD].w = SANE_FIX (50);
|
||||
|
||||
/* BW threshold curve */
|
||||
s->opt[OPT_THRESHOLD_CURVE].name = "threshold-curve";
|
||||
s->opt[OPT_THRESHOLD_CURVE].title = SANE_I18N ("Threshold curve");
|
||||
s->opt[OPT_THRESHOLD_CURVE].desc = SANE_I18N ("Dynamic threshold curve, from light to dark, normally 50-65");
|
||||
s->opt[OPT_THRESHOLD_CURVE].type = SANE_TYPE_INT;
|
||||
s->opt[OPT_THRESHOLD_CURVE].unit = SANE_UNIT_NONE;
|
||||
s->opt[OPT_THRESHOLD_CURVE].constraint_type = SANE_CONSTRAINT_RANGE;
|
||||
s->opt[OPT_THRESHOLD_CURVE].constraint.range = &threshold_curve_range;
|
||||
s->val[OPT_THRESHOLD_CURVE].w = 50;
|
||||
|
||||
/* dynamic linart */
|
||||
s->opt[OPT_DYNAMIC_LINEART].name = "dynamic-lineart";
|
||||
s->opt[OPT_DYNAMIC_LINEART].title = SANE_I18N ("Dynamic lineart");
|
||||
|
@ -6356,6 +6478,7 @@ get_option_value (Genesys_Scanner * s, int option, void *val)
|
|||
case OPT_BR_X:
|
||||
case OPT_BR_Y:
|
||||
case OPT_THRESHOLD:
|
||||
case OPT_THRESHOLD_CURVE:
|
||||
case OPT_DYNAMIC_LINEART:
|
||||
case OPT_DISABLE_INTERPOLATION:
|
||||
case OPT_LAMP_OFF_TIME:
|
||||
|
@ -6466,6 +6589,7 @@ set_option_value (Genesys_Scanner * s, int option, void *val,
|
|||
case OPT_RESOLUTION:
|
||||
case OPT_BIT_DEPTH:
|
||||
case OPT_THRESHOLD:
|
||||
case OPT_THRESHOLD_CURVE:
|
||||
case OPT_DYNAMIC_LINEART:
|
||||
case OPT_DISABLE_INTERPOLATION:
|
||||
case OPT_PREVIEW:
|
||||
|
@ -6489,12 +6613,14 @@ set_option_value (Genesys_Scanner * s, int option, void *val,
|
|||
if (strcmp (s->val[option].s, SANE_VALUE_SCAN_MODE_LINEART) == 0)
|
||||
{
|
||||
ENABLE (OPT_THRESHOLD);
|
||||
ENABLE (OPT_THRESHOLD_CURVE);
|
||||
DISABLE (OPT_BIT_DEPTH);
|
||||
ENABLE (OPT_COLOR_FILTER);
|
||||
}
|
||||
else
|
||||
{
|
||||
DISABLE (OPT_THRESHOLD);
|
||||
DISABLE (OPT_THRESHOLD_CURVE);
|
||||
if (strcmp (s->val[option].s, SANE_VALUE_SCAN_MODE_GRAY) == 0)
|
||||
{
|
||||
ENABLE (OPT_COLOR_FILTER);
|
||||
|
|
|
@ -92,6 +92,7 @@ enum Genesys_Option
|
|||
OPT_EXTRAS_GROUP,
|
||||
OPT_LAMP_OFF_TIME,
|
||||
OPT_THRESHOLD,
|
||||
OPT_THRESHOLD_CURVE,
|
||||
OPT_DYNAMIC_LINEART,
|
||||
OPT_DISABLE_INTERPOLATION,
|
||||
OPT_COLOR_FILTER,
|
||||
|
|
|
@ -89,30 +89,89 @@ genesys_reverse_bits(
|
|||
return SANE_STATUS_GOOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* uses the threshold/threshold_curve to control software binarization
|
||||
* This code was taken from the epjistsu backend by m. allan noah <kitno455 at gmail dot com>
|
||||
* @param dev device set up for the scan
|
||||
* @param src pointer to raw data
|
||||
* @param dst pointer where to store result
|
||||
* @param width width of the processed line
|
||||
* */
|
||||
static SANE_Status
|
||||
binarize_line(Genesys_Device * dev, uint8_t *src, uint8_t *dst, int width)
|
||||
{
|
||||
int j, windowX, sum = 0;
|
||||
int thresh;
|
||||
int offset, addCol, dropCol;
|
||||
unsigned char mask;
|
||||
|
||||
/* ~1mm works best, but the window needs to have odd # of pixels */
|
||||
windowX = 6 * dev->settings.xres / 150;
|
||||
if (!(windowX % 2))
|
||||
windowX++;
|
||||
|
||||
/* second, prefill the sliding sum */
|
||||
for (j = 0; j < windowX; j++)
|
||||
sum += src[j];
|
||||
|
||||
/* third, walk the input buffer, update the sliding sum, */
|
||||
/* determine threshold, output bits */
|
||||
for (j = 0; j < width; j++)
|
||||
{
|
||||
/* output image location */
|
||||
offset = j % 8;
|
||||
mask = 0x80 >> offset;
|
||||
thresh = dev->settings.threshold;
|
||||
|
||||
/* move sum/update threshold only if there is a curve */
|
||||
if (dev->settings.threshold_curve)
|
||||
{
|
||||
addCol = j + windowX / 2;
|
||||
dropCol = addCol - windowX;
|
||||
|
||||
if (dropCol >= 0 && addCol < width)
|
||||
{
|
||||
sum -= src[dropCol];
|
||||
sum += src[addCol];
|
||||
}
|
||||
thresh = dev->lineart_lut[sum / windowX];
|
||||
}
|
||||
|
||||
/* use average to lookup threshold */
|
||||
if (src[j] > thresh)
|
||||
*dst &= ~mask; /* white */
|
||||
else
|
||||
*dst |= mask; /* black */
|
||||
|
||||
if (offset == 7)
|
||||
dst++;
|
||||
}
|
||||
|
||||
return SANE_STATUS_GOOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* software lineart using data from a 8 bit gray scan. We assume true gray
|
||||
* or monochrome scan as input.
|
||||
*/
|
||||
static SANE_Status
|
||||
genesys_gray_lineart(
|
||||
Genesys_Device *dev,
|
||||
uint8_t *src_data,
|
||||
uint8_t *dst_data,
|
||||
size_t pixels,
|
||||
size_t channels,
|
||||
size_t lines,
|
||||
uint8_t threshold)
|
||||
{
|
||||
size_t x,y,c,b;
|
||||
size_t y;
|
||||
|
||||
DBG ( DBG_io2, "genesys_gray_lineart: converting %d lines of %d pixels\n", lines, pixels);
|
||||
for(y = 0; y < lines; y++) {
|
||||
for(x = 0; x < pixels; x+=8) {
|
||||
for(c = 0; c < channels; c++)
|
||||
*(dst_data + c) = 0;
|
||||
for(b = 0; b < 8 && x+b < pixels; b++) {
|
||||
for(c = 0; c < channels; c++) {
|
||||
if (*src_data++ < threshold)
|
||||
*(dst_data + c) |= (0x80 >> b);
|
||||
}
|
||||
}
|
||||
dst_data += channels;
|
||||
}
|
||||
}
|
||||
|
||||
for(y = 0; y < lines; y++)
|
||||
{
|
||||
binarize_line(dev, src_data+y*pixels, dst_data, pixels);
|
||||
dst_data += pixels/8;
|
||||
}
|
||||
return SANE_STATUS_GOOD;
|
||||
}
|
||||
|
||||
|
|
|
@ -503,6 +503,9 @@ typedef struct
|
|||
/**> lineart threshold */
|
||||
int threshold;
|
||||
|
||||
/**> lineart threshold curve for dynamic rasterization */
|
||||
int threshold_curve;
|
||||
|
||||
/**> Disable interpolation for xres<yres*/
|
||||
int disable_interpolation;
|
||||
|
||||
|
@ -581,7 +584,7 @@ struct Genesys_Device
|
|||
SANE_Int lamp_off_time;
|
||||
|
||||
SANE_Bool read_active;
|
||||
SANE_Bool document; /* for sheetfed scanner's, is TRUE when there
|
||||
SANE_Bool document; /**> for sheetfed scanner's, is TRUE when there
|
||||
is a document in the scanner */
|
||||
|
||||
Genesys_Buffer read_buffer;
|
||||
|
@ -596,6 +599,9 @@ struct Genesys_Device
|
|||
size_t wpl; /* asic's word per line */
|
||||
|
||||
Genesys_Current_Setup current_setup; /* contains the real used values */
|
||||
|
||||
/**> look up table used in dynamic rasterization */
|
||||
unsigned char lineart_lut[256];
|
||||
|
||||
Genesys_Calibration_Cache *calibration_cache;
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue