最近由于工作需要,要写一套扫描相关的接口。
在这里记录一下,实现还有有点复杂的。
目录
sudo apt install libsane-dev sane-utils
我们在操作扫描仪之前需要初始化才能正常使用。
初始化使用的是sane里的sane_init。
- void scanner_init()
- {
- printf("[%s] Start\n", __FUNCTION__);
- SANE_Int version_code = 0;
- sane_init(&version_code, auth_callback);
- printf("SANE version code: %d\n", version_code);
- }
-
- static void
- auth_callback(SANE_String_Const resource,
- SANE_Char *username, SANE_Char *password)
- {
- }
初始化成功则version_code为SANE_STATUS_GOOD(0)。
核心是sane_get_devices函数。
先通过sane_get_devices获取扫描仪列表,然后申请一个二维数组,将扫描仪列表放入二维数组中返回。
- const char **scanner_get_available_list()
- {
- printf("[%s] Start\n", __FUNCTION__);
- SANE_Status status;
- SANE_Int num_devices = 0;
- const SANE_Device **device_list;
-
- //获取扫描仪列表
- status = sane_get_devices(&device_list, SANE_FALSE);
- if (status != SANE_STATUS_GOOD)
- {
- printf("Error getting device list: %s\n", sane_strstatus(status));
- return NULL;
- }
-
- // 获取当前设备数量
- while (device_list[num_devices])
- num_devices++;
-
- // 如果设备列表为空,返回
- if (num_devices == 0)
- {
- printf("No scanners found.\n");
- sane_exit();
- return NULL;
- }
-
- // 分配内存
- const char **scanner_list = malloc(sizeof(SANE_Device *) * (num_devices + 1));
- if (!scanner_list)
- {
- printf("Failed to allocate memory.\n");
- sane_exit();
- return NULL;
- }
-
- // 继续分配内存
- for (int i = 0; i < num_devices; i++)
- {
- scanner_list[i] = strdup(device_list[i]->name);
- if (!scanner_list[i])
- {
- printf("Failed to allocate memory.\n");
- for (int j = 0; j < i; j++)
- {
- free(scanner_list[j]);
- }
- free(scanner_list);
- sane_exit();
- return NULL;
- }
- }
- scanner_list[num_devices] = NULL;
-
- // 返回设备列表
- return scanner_list;
- }
我们再把获取到的设备列表循环打印一下。
- const char **scanner_list = scanner_get_available_list();
-
- if (scanner_list != NULL)
- {
- for (int num_devices = 0; scanner_list[num_devices]; ++num_devices)
- {
- if (scanner_list[num_devices] != NULL) // 添加一个检查
- {
- printf("Device %d: name=%s \n",
- num_devices, scanner_list[num_devices]);
- }
- }
- }
- else
- {
- return;
- }
这里介绍一下sane_open,第一个参数是扫描仪的名称,第二个参数是一个空的句柄,打开后通过句柄进行后续操作。
- extern SANE_Status sane_open (SANE_String_Const devicename,
- SANE_Handle * handle);
函数的具体实现如下:
- static SANE_Handle sane_handle = NULL; // 扫描仪设备句柄,全局变量
-
- int scanner_open_device(char *scanner_name)
- {
- printf("[%s] Start\n", __FUNCTION__);
- SANE_Status sane_status = 0;
-
- if (sane_status = sane_open(scanner_name, &sane_handle))
- {
- printf("sane_open status: %s\n", sane_strstatus(sane_status));
- }
- if (sane_status != SANE_STATUS_GOOD)
- sane_handle = NULL;
-
- return sane_status;
- }
这里的入参scanner_name是扫描仪列表的scanner_list,如果要打开第一个扫描仪的话是scanner_list[0]。
如果函数的返回值不是SANE_STATUS_GOOD,表示打开失败了。
扫描的所有参数都是通过sane_control_option实现的,每个参数的功能详见备注。
- extern SANE_Status sane_control_option (SANE_Handle handle, //sane_open的句柄
- SANE_Int option, //要设置选项的序号,2是颜色,3是分辨率
- SANE_Action action, //操作的类型,给选项赋值或者获取当前值
- void *value, //value的实际值
- SANE_Int *info); //没发现有什么用
-
操作的类型一共有以下三种,我们这里只用到第二种。
- typedef enum
- {
- SANE_ACTION_GET_VALUE = 0,
- SANE_ACTION_SET_VALUE,
- SANE_ACTION_SET_AUTO
- }
- SANE_Action;
我这里设置了颜色,扫描的分辨率和纸张大小,还有很多可以设置的选项,可以自行探索。
- static SANE_Handle sane_handle = NULL; // 扫描仪设备句柄,全局变量
-
- // 设置指定扫描仪颜色,通过传入参数val_color进行设置扫描设备的颜色
- int scanner_set_color(SANE_String val_color)
- {
- printf("[%s] Start!\n", __FUNCTION__);
- SANE_Status status;
- status = sane_control_option(sane_handle, 2,
- SANE_ACTION_SET_VALUE, val_color, NULL);
-
- if (status != SANE_STATUS_GOOD)
- {
- printf("Option did not set, desc = %s\n", sane_strstatus(status));
-
- return status;
- }
-
- printf("set color option success!\n");
- return status;
- }
-
-
- // 设置指定扫描仪扫描的分辨率(清晰程度,分辨率越大越清晰)
- int scanner_set_resolutions(SANE_Int val_resolution)
- {
- printf("[%s] Start!\n", __FUNCTION__);
- SANE_Status status;
-
- status = sane_control_option(sane_handle, 3, SANE_ACTION_SET_VALUE, &val_resolution, NULL);
- if (status != SANE_STATUS_GOOD)
- {
- printf("Option did not set, desc = %s\n", sane_strstatus(status));
- return status;
- }
-
- printf("set resolution option success!\n");
- return status;
- }
设置纸张大小有点复杂,因为纸张大小没有对应的option。因此曲线救国,通过设置扫描的纸张的长和宽来实现。长和宽的option序号分别是9和10。
- enum sizes_type
- {
- A2 = 1,
- A3,
- A4,
- A5,
- A6
- };
-
- static double g_saneSizeA4BrY = 297;
-
- int scanner_set_size(SANE_String size)
- {
- printf("[%s] Start!\n", __FUNCTION__);
- SANE_Status status = SANE_STATUS_GOOD;
- int type;
-
- if (!strcmp("A2", size))
- {
- type = A2;
- }
- else if (!strcmp("A3", size))
- {
- type = A3;
- }
- else if (!strcmp("A4", size))
- {
- type = A4;
- }
- else if (!strcmp("A5", size))
- {
- type = A5;
- }
- else if (!strcmp("A6", size))
- {
- type = A6;
- }
- else
- {
- type = 0;
- }
-
- switch (type)
- {
- case A2:
- status = kdk_scanner_set_size_real(sane_handle, 420, 594);
- break;
- case A3:
- status = kdk_scanner_set_size_real(sane_handle, 297, 420);
- break;
- case A4:
- status = kdk_scanner_set_size_real(sane_handle, 210, g_saneSizeA4BrY);
- break;
- case A5:
- status = kdk_scanner_set_size_real(sane_handle, 148, 210);
- break;
- case A6:
- status = kdk_scanner_set_size_real(sane_handle, 105, 144);
- break;
- default:
- status = SANE_STATUS_UNSUPPORTED;
- }
-
- return status;
- }
-
- /**
- * @brief scanner_set_size_real 统一设置扫描设备尺寸
- *
- * @param sane_handle 扫描句柄
- *
- * @param val_size_br_x 扫描设备右下角的x坐标
- *
- * @param val_size_br_y 扫描设备右下角的y坐标
- *
- * @return 返回扫描设备设置尺寸的情况
- */
- SANE_Status scanner_set_size_real(SANE_Handle sane_handle, SANE_Int val_size_br_x,
- SANE_Int val_size_br_y)
- {
- printf("[%s] Start!\n", __FUNCTION__);
- SANE_Status status = SANE_STATUS_GOOD;
-
- SANE_Word x = SANE_FIX(val_size_br_x);
- SANE_Word y = SANE_FIX(val_size_br_y);
-
- SANE_Word zero = SANE_FIX(0.0);
- status = sane_control_option(sane_handle, 7, SANE_ACTION_SET_VALUE, &zero, NULL);
- if (status != SANE_STATUS_GOOD)
- {
- return status;
- }
-
- status = sane_control_option(sane_handle, 8, SANE_ACTION_SET_VALUE, &zero, NULL);
- if (status != SANE_STATUS_GOOD)
- {
- return status;
- }
-
- status = sane_control_option(sane_handle, 9,
- SANE_ACTION_SET_VALUE, &x, NULL);
-
- if (status != SANE_STATUS_GOOD)
- {
- printf("Option x did not set, desc = %s\n", sane_strstatus(status));
-
- return status;
- }
-
- status = sane_control_option(sane_handle, 10,
- SANE_ACTION_SET_VALUE, &y, NULL);
-
- if (status != SANE_STATUS_GOOD)
- {
- printf("Option y did not set, desc = %s\n", sane_strstatus(status));
-
- return status;
- }
-
- return status;
- }
和前面一样,如果函数的返回值不是SANE_STATUS_GOOD,表示失败了。
扫描我分成两类,分为单页单面扫描和多页双面扫描。
单页和多页也是一种可以设置的扫描属性,单页和多页的主要是这个这个属性的区别,别的部分都差不多。
- //设置扫描是单页还是多页
- int scanner_set_page_type(SANE_Int type)
- {
- printf("[%s] Start!\n", __FUNCTION__);
- SANE_Status status;
-
- //对应的option序号为4
- status = sane_control_option(sane_handle, 4,
- SANE_ACTION_SET_VALUE, &type, NULL);
-
- if (status != SANE_STATUS_GOOD)
- {
- printf("Option did not set, desc = %s\n", sane_strstatus(status));
-
- return status;
- }
-
- printf("set page type option success!\n");
- return status;
- }
单页单面扫描,就是不管有多少页都只扫描第一页的第一面。
- /**
- * @brief 指定扫描仪进行扫描(统一按照多页,双面处理)
- *
- * @param fileName:保存扫描文件的文件名,比如传test的话,扫描后的文件会是test_1,test_2之类的形式
- *
- * @param type:扫描类型 0:单面单面扫描,1:多页双面扫描
- *
- * @return 操作的返回值,0或者7为成功,其他为失败
- */
- int scanner_start_scan(SANE_String_Const fileName, int type)
- {
- printf("[%s] Start\n", __FUNCTION__);
- SANE_Status sane_status = 0;
-
- switch (type)
- {
- case 0:
- return do_scan_one(fileName);
-
- case 1:
- return do_scan_all(fileName);
-
- default:
- return do_scan_all(fileName);
- }
- }
-
这是扫描单页的接口
- // 单页扫描
- SANE_Status do_scan_one(const char *fileName)
- {
- printf("[%s] Start\n", __FUNCTION__);
-
- del_old_pic();//扫描之前删掉上一次的内容
-
- SANE_Status status;
- FILE *ofp = NULL;
- char path[PATH_MAX];
- char part_path[PATH_MAX];
- buffer_size = (32 * 1024);
- buffer = (SANE_Byte *)malloc(buffer_size);
- int i = 1;
-
- // 设置打印机单页进纸张
- status = kdk_scanner_set_page_type(1);
- if (status != SANE_STATUS_GOOD)
- {
- printf("set page type fail:%s\n", sane_strstatus(status));
- return status;
- }
-
- do
- {
- // 设置保存路径
- sprintf(path, "/tmp/%s-%d.pnm", fileName, i); // 格式化PNM文件路径
- strcpy(part_path, path);
- strcat(part_path, ".part");
-
- printf("picture name: %s\n", path);
-
- // 开始扫描
- status = sane_start(sane_handle);
- if (status != SANE_STATUS_GOOD)
- {
- break;
- }
-
- if (NULL == (ofp = fopen(part_path, "w")))
- {
- status = SANE_STATUS_ACCESS_DENIED;
- break;
- }
-
- // 保存扫描数据
- status = scan_it(ofp);
-
- switch (status)
- {
- case SANE_STATUS_GOOD:
- case SANE_STATUS_EOF:
- {
- status = SANE_STATUS_GOOD;
- if (!ofp || 0 != fclose(ofp))
- {
- status = SANE_STATUS_ACCESS_DENIED;
- break;
- }
- else
- {
- ofp = NULL;
- if (rename(part_path, path))
- {
- status = SANE_STATUS_ACCESS_DENIED;
- break;
- }
- }
- }
- break;
- default:
- break;
- }
- } while (0);
-
-
- if (ofp)
- {
- fclose(ofp);
- ofp = NULL;
- }
- if (buffer)
- {
- free(buffer);
- buffer = NULL;
- }
-
- return status;
- }
-
- // 删除上一次扫描的文件
- void del_old_pic()
- {
- DIR *dir;
- struct dirent *entry;
- char path[] = "/tmp/";
- char ext[] = ".pnm";
-
- dir = opendir(path);
- if (dir == NULL)
- {
- perror("Unable to open directory");
- exit(EXIT_FAILURE);
- }
-
- while ((entry = readdir(dir)) != NULL)
- {
- // Check if the entry is a file and ends with .pnm
- if (entry->d_type == DT_REG &&
- strstr(entry->d_name, ext) != NULL &&
- strcmp(entry->d_name + strlen(entry->d_name) - strlen(ext), ext) == 0)
- {
- char full_path[512];
- sprintf(full_path, "%s%s", path, entry->d_name);
-
- if (remove(full_path) == 0)
- {
- printf("Deleted %s\n", full_path);
- }
- else
- {
- perror("Unable to delete file");
- }
- }
- }
-
- closedir(dir);
- }
-
- // sane 设置扫描方式
- int kdk_scanner_set_page_type(SANE_Int type)
- {
- printf("[%s] Start!\n", __FUNCTION__);
- SANE_Status status;
- status = sane_control_option(sane_handle, 4,
- SANE_ACTION_SET_VALUE, &type, NULL);
-
- if (status != SANE_STATUS_GOOD)
- {
- printf("Option did not set, desc = %s\n", sane_strstatus(status));
-
- return status;
- }
-
- printf("set page type option success!\n");
- return status;
- }
保存图片,这一部分细节很多,我也没仔细研究,直接用就行。
- static SANE_Status scan_it(FILE *ofp)
- {
- int i, len, first_frame = 1, offset = 0, must_buffer = 0, hundred_percent;
- SANE_Byte min = 0xff, max = 0;
- SANE_Parameters parm;
- SANE_Status status;
- Image image = {0, 0, 0, 0, 0};
- static const char *format_name[] = {"gray", "RGB", "red", "green", "blue"};
- SANE_Word total_bytes = 0, expected_bytes;
- SANE_Int hang_over = -1;
-
- do
- {
-
- if (!first_frame)
- {
- status = sane_start(sane_handle);
- if (status != SANE_STATUS_GOOD)
- {
- goto cleanup;
- }
- }
-
- status = sane_get_parameters(sane_handle, &parm);
- if (status != SANE_STATUS_GOOD)
- {
- goto cleanup;
- }
-
- if (first_frame)
- {
- switch (parm.format)
- {
- case SANE_FRAME_RED:
- case SANE_FRAME_GREEN:
- case SANE_FRAME_BLUE:
- assert(parm.depth == 8);
- must_buffer = 1;
- offset = parm.format - SANE_FRAME_RED;
- break;
- case SANE_FRAME_RGB:
- assert((parm.depth == 8) || (parm.depth == 16));
- case SANE_FRAME_GRAY:
- assert((parm.depth == 1) || (parm.depth == 8) || (parm.depth == 16));
- if (parm.lines < 0)
- {
- must_buffer = 1;
- offset = 0;
- }
- else
- {
- write_pnm_header(parm.format, parm.pixels_per_line, parm.lines, parm.depth, ofp);
- }
- break;
- default:
- break;
- }
-
- if (must_buffer)
- {
- image.width = parm.bytes_per_line;
- if (parm.lines >= 0)
- image.height = parm.lines - STRIP_HEIGHT + 1;
- else
- image.height = 0;
-
- image.x = image.width - 1;
- image.y = -1;
- if (!advance(&image))
- {
- status = SANE_STATUS_NO_MEM;
- goto cleanup;
- }
- }
- }
- else
- {
- assert(parm.format >= SANE_FRAME_RED && parm.format <= SANE_FRAME_BLUE);
- offset = parm.format - SANE_FRAME_RED;
- image.x = image.y = 0;
- }
-
- hundred_percent = parm.bytes_per_line * parm.lines * ((parm.format == SANE_FRAME_RGB || parm.format == SANE_FRAME_GRAY) ? 1 : 3);
-
- // 这段是写图片数据
- while (1)
- {
- double progr;
- status = sane_read(sane_handle, buffer, buffer_size, &len);
- total_bytes += (SANE_Word)len;
- progr = ((total_bytes * 100.) / (double)hundred_percent);
- if (progr > 100.)
- progr = 100.;
-
- if (status != SANE_STATUS_GOOD)
- {
- if (status != SANE_STATUS_EOF)
- {
- return status;
- }
- break;
- }
-
- if (must_buffer)
- {
- switch (parm.format)
- {
- case SANE_FRAME_RED:
- case SANE_FRAME_GREEN:
- case SANE_FRAME_BLUE:
- for (i = 0; i < len; ++i)
- {
- image.data[offset + 3 * i] = buffer[i];
- if (!advance(&image))
- {
- status = SANE_STATUS_NO_MEM;
- goto cleanup;
- }
- }
- offset += 3 * len;
- break;
- case SANE_FRAME_RGB:
- for (i = 0; i < len; ++i)
- {
- image.data[offset + i] = buffer[i];
- if (!advance(&image))
- {
- status = SANE_STATUS_NO_MEM;
- goto cleanup;
- }
- }
- offset += len;
- break;
- case SANE_FRAME_GRAY:
- for (i = 0; i < len; ++i)
- {
- image.data[offset + i] = buffer[i];
- if (!advance(&image))
- {
- status = SANE_STATUS_NO_MEM;
- goto cleanup;
- }
- }
- offset += len;
- break;
- default:
- break;
- }
- }
- else /* ! must_buffer */
- {
- if ((parm.depth != 16))
- fwrite(buffer, 1, len, ofp);
- else
- {
- #if !defined(WORDS_BIGENDIAN)
- int i, start = 0;
- /* check if we have saved one byte from the last sane_read */
- if (hang_over > -1)
- {
- if (len > 0)
- {
- fwrite(buffer, 1, 1, ofp);
- buffer[0] = (SANE_Byte)hang_over;
- hang_over = -1;
- start = 1;
- }
- }
- /* now do the byte-swapping */
- for (i = start; i < (len - 1); i += 2)
- {
- unsigned char LSB;
- LSB = buffer[i];
- buffer[i] = buffer[i + 1];
- buffer[i + 1] = LSB;
- }
- /* check if we have an odd number of bytes */
- if (((len - start) % 2) != 0)
- {
- hang_over = buffer[len - 1];
- len--;
- }
- #endif
- fwrite(buffer, 1, len, ofp);
- }
- }
-
- if (verbose && parm.depth == 8)
- {
- for (i = 0; i < len; ++i)
- if (buffer[i] >= max)
- max = buffer[i];
- else if (buffer[i] < min)
- min = buffer[i];
- }
- }
- first_frame = 0;
- } while (!parm.last_frame);
-
- if (must_buffer)
- {
- image.height = image.y;
- write_pnm_header(parm.format, parm.pixels_per_line, image.height, parm.depth, ofp);
-
- #if !defined(WORDS_BIGENDIAN)
- if (parm.depth == 16)
- {
- int i;
- for (i = 0; i < image.height * image.width; i += 2)
- {
- unsigned char LSB;
- LSB = image.data[i];
- image.data[i] = image.data[i + 1];
- image.data[i + 1] = LSB;
- }
- }
- #endif
- fwrite(image.data, 1, image.height * image.width, ofp);
- }
-
- fflush(ofp);
-
- cleanup:
- if (image.data)
- free(image.data);
-
- return status;
- }
-
- void write_pnm_header(SANE_Frame format, int width, int height, int depth, FILE *ofp)
- {
- printf("[%s] Start\n", __FUNCTION__);
- switch (format)
- {
- case SANE_FRAME_RED:
- case SANE_FRAME_GREEN:
- case SANE_FRAME_BLUE:
- case SANE_FRAME_RGB:
- fprintf(ofp, "P6\n# SANE data follows\n%d %d\n%d\n", width, height, (depth <= 8) ? 255 : 65535);
- break;
- default:
- if (depth == 1)
- fprintf(ofp, "P4\n# SANE data follows\n%d %d\n", width, height);
- else
- fprintf(ofp, "P5\n# SANE data follows\n%d %d\n%d\n", width, height, (depth <= 8) ? 255 : 65535);
- break;
- }
- }
-
- static void *
- advance(Image *image)
- {
- if (++image->x >= image->width)
- {
- image->x = 0;
- if (++image->y >= image->height || !image->data)
- {
- size_t old_size = 0, new_size;
-
- if (image->data)
- old_size = image->height * image->width;
-
- image->height += STRIP_HEIGHT;
- new_size = image->height * image->width;
-
- if (image->data)
- image->data = realloc(image->data, new_size);
- else
- image->data = malloc(new_size);
- if (image->data)
- memset(image->data + old_size, 0, new_size - old_size);
- }
- }
- if (!image->data)
- fprintf(stderr, "can't allocate image buffer (%dx%d)\n",
- image->width, image->height);
- return image->data;
- }
双页扫描,用do_scan_all替换do_scan_one,其他的函数都一样。
- // 双面扫描全部文件+保存为PNM图像格式
- SANE_Status do_scan_all(const char *fileName)
- {
- printf("[%s] Start\n", __FUNCTION__);
-
- SANE_Status status; // 返回状态
- FILE *ofp = NULL; // 输出文件指针
- char path[PATH_MAX]; // PNM文件路径
- char part_path[PATH_MAX]; // 临时PNN文件路径
- buffer_size = (32 * 1024); // 缓冲区大小
- buffer = malloc(buffer_size); // 动态分配缓冲区
- int i = 1;
-
- del_old_pic();
-
- //设置打印机多页进纸张
- status = kdk_scanner_set_page_type(0);
- if (status != SANE_STATUS_GOOD)
- {
- printf("set page type fail:%s\n", sane_strstatus(status));
- return status;
- }
-
- do
- {
- sprintf(path, "/tmp/%s-%d.pnm", fileName, i); // 格式化PNM文件路径
- strcpy(part_path, path); // 复制PNM文件路径到临时文件路径
- strcat(part_path, ".part"); // 在临时文件路径后添加扩展名".part"
-
- // 启动扫描过程
- status = sane_start(sane_handle);
- if (status != SANE_STATUS_GOOD)
- {
- break;
- }
-
- // 创建临时文件
- if (NULL == (ofp = fopen(part_path, "w")))
- {
- status = SANE_STATUS_ACCESS_DENIED;
- break;
- }
-
- // 进行扫描,并将结果写入到临时文件中
- status = scan_it(ofp);
-
- switch (status)
- {
- case SANE_STATUS_GOOD:
- case SANE_STATUS_EOF:
- {
- // 扫描成功或结束
- status = SANE_STATUS_GOOD;
-
- // 关闭临时文件,并检查是否成功关闭
- if (!ofp || 0 != fclose(ofp))
- {
- status = SANE_STATUS_ACCESS_DENIED;
- break;
- }
- else
- {
- ofp = NULL; // 将文件指针设置为NULL,避免重复关闭
- // 将临时文件重命名为正式的PNM文件
- if (rename(part_path, path))
- {
- status = SANE_STATUS_ACCESS_DENIED;
- break;
- }
- }
- }
- break;
- default:
- break;
- }
- i++;
- } while (status == SANE_STATUS_GOOD);
-
- // 如果出现错误,则取消扫描进程
- if (SANE_STATUS_GOOD != status)
- {
- sane_cancel(sane_handle);
- }
-
- // 关闭输出文件
- if (ofp)
- {
- fclose(ofp);
- ofp = NULL;
- }
-
- // 释放缓冲区内存
- if (buffer)
- {
- free(buffer);
- buffer = NULL;
- }
-
- if ((status == SANE_STATUS_NO_DOCS) && (i > 1))
- status = SANE_STATUS_GOOD;
-
- return status; // 返回状态
- }
扫描完成会会在tmp下生成扫描文件。
- void scanner_close_device()
- {
- printf("[%s] Start\n", __FUNCTION__);
- if (sane_handle != NULL)
- {
- sane_close(sane_handle);
- }
- sane_handle = NULL;
- }
- void scanner_exit()
- {
- printf("[%s] Start\n", __FUNCTION__);
- sane_exit();
- }
Linux下通用扫描仪API——SANE( Scanner Access Now Easy)_linux sane-CSDN博客