1

我在嵌入式设备的固件上工作(用 C 编写),我需要从显示器上截取屏幕截图并将其保存为 bmp 文件。目前我正在研究生成 bmp 文件数据的模块。最简单的方法是编写一些带有以下参数的函数:

(为简单起见,我的示例中仅支持带有索引颜色的图像)

  • 颜色深度
  • 图像尺寸(宽、高)
  • 指向函数的指针以获取 color_index (i) 的调色板颜色
  • 指向函数的指针,以获取具有给定坐标 (x, y) 的像素的 color_index
  • 指向写入图像数据的函数的指针

然后这个函数的用户应该这样调用它:

/*
 * Assume we have the following functions:
 *    int_least32_t palette_color_get  (int color_index);
 *    int           pix_color_idx_get  (int x, int y);
 *    void          data_write         (const char *p_data, size_t len);
 */

bmp_file_generate(
      1,         //-- color_depth
      x, y,      //-- size
      palette_color_get,
      pic_color_idx_get,
      data_write
      );

就是这样:这个函数完成所有工作,并且仅在工作完成时返回(即由给定的用户回调函数生成和“写入”的 bmp 文件data_write()

但是,我需要使bmp_writer模块在协作 RTOS 中可用,并且data_write()可能是一个通过某种协议(例如,UART)实际将数据传输到另一个设备的函数),因此,这个函数只需要从任务上下文中调用。这种方法不起作用,我需要以 OO 风格制作它,它的用法应该如下所示:

/*
 * create instance of bmp_writer with needed params
 * (we don't need "data_write" pointer anymore)
 */
T_BmpWriter *p_bmp_writer = new_bmp_writer(
      1,         //-- color_depth
      x, y,      //-- size
      palette_color_get,
      pic_color_idx_get
      );

/*
 * Now, byte-by-byte get all the data!
 */
while (bmp_writer__data_available(p_bmp_writer) > 0){
   char cur_char = bmp_writer__get_next_char(p_bmp_writer);
   //-- do something useful with current byte (i.e. cur_char).
   //   maybe transmit to another device, or save to flash, or anything.
}

/*
 * Done! Free memory now.
 */ 
delete_bmp_writer(p_bmp_writer);

如您所见,用户可以bmp_writer__get_next_char(p_bmp_writer)在需要时调用,并根据需要处理接收到的数据。

实际上我已经实现了这个,但是,通过这种方法,所有的算法都被彻底颠覆了,而且这段代码非常不可读。

我将向您展示生成调色板数据的旧代码的一部分(来自完成所有工作的函数,并且仅在工作完成时返回),以及新代码的适当部分(以状态机样式)。

旧代码:

void bmp_file_generate(/*....args....*/)
{
   //-- ... write headers

   //-- write palette (if needed)
   if (palette_colors_cnt > 0){
      size_t i;
      int_least32_t cur_color;
      for (i = 0; i < palette_colors_cnt; i++){
         cur_color = callback_palette_color_get(i);
         callback_data_write((const char *)&cur_color, sizeof(cur_color));
      }
   }

   //-- ...... write image data ..........
}

如您所见,代码非常简短易读。

现在,新代码。

它看起来像状态机,因为它实际上是按阶段划分的(HEADER_WRITE、PALETTE_WRITE、IMG_DATA_WRITE),每个阶段都有自己的上下文。在旧代码中,上下文保存在局部变量中,但现在我们需要创建结构并从堆中分配它。所以:

/*
 * Palette stage context
 */
typedef struct {
   size_t         i;
   size_t         cur_color_idx;
   int_least32_t  cur_color;
} T_StageContext_Palette;

/*
 * Function that switches stage.
 * T_BmpWriter is an object context, and pointer *me is analogue of "this" in OO-languages.
 * bool_start is 1 if stage is just started, and 0 if it is finished.
 */
static void _stage_start_end(T_BmpWriter *me, U08 bool_start)
{
   switch (me->stage){
      //-- ...........other stages.........

      case BMP_WR_STAGE__PALETTE:
         if (bool_start){
            //-- palette stage is just started. Allocate stage context and initialize it.
            me->p_stage_context = malloc(sizeof(T_StageContext_Palette));
            memset(me->p_stage_context, 0x00, sizeof(T_StageContext_Palette));

            //-- we need to get first color, so, set index of byte in cur_color to maximum
            ((T_StageContext_Palette *)me->p_stage_context)->i = sizeof(int_least32_t);
         } else {
            free(me->p_stage_context);
            me->p_stage_context = NULL;
         }
         break;

      //-- ...........other stages.........

   }
}


/*
 * Function that turns to the next stage
 */
static void _next_stage(T_BmpWriter *me)
{
   _stage_start_end(me, 0);
   me->stage++;
   _stage_start_end(me, 1);
}

/*
 * Function that actually does the job and returns next byte
 */
U08 bmp_writer__get_next_char(T_BmpWriter *me)
{
   U08 ret = 0;        //-- resulting byte to return
   U08 bool_ready = 0; //-- flag if byte is ready

   while (!bool_ready){
      switch (me->stage){

         //-- ...........other stages.........

         case BMP_WR_STAGE__PALETTE:
            {
               T_StageContext_Palette *p_stage_context =
                  (T_StageContext_Palette *)me->p_stage_context;

               if (p_stage_context->i < sizeof(int_least32_t)){
                  //-- return byte of cur_color
                  ret = *( (U08 *)&p_stage_context->cur_color + p_stage_context->i );
                  p_stage_context->i++;
                  bool_ready = 1;
               } else {
                  //-- need to get next color (or even go to next stage)

                  if (p_stage_context->cur_color_idx < me->bmp_details.palette_colors_cnt){
                     //-- next color
                     p_stage_context->cur_color = me->callback.p_palette_color_get(
                           me->callback.user_data,
                           p_stage_context->cur_color_idx
                           );
                     p_stage_context->cur_color_idx++;
                     p_stage_context->i = 0;
                  } else {
                     //-- next stage!
                     _next_stage(me);
                  }
               }

            }
            break;

         //-- ...........other stages.........

      }
   }

   return ret;

}

如此庞大的代码,很难理解!但我真的不知道如何以某种不同的方式制作它,以便能够逐字节获取信息。

有谁知道如何实现这一点,并保持代码的可读性?

任何帮助表示赞赏。

4

1 回答 1

4

您可以尝试protothread,这对于将基于状态机的程序转换为线程式程序很有用。我不是 100% 确定它可以优雅地解决你的问题,你可以试一试。这篇论文是一个很好的起点:Protothreads: simplifying event-driven programming of memory-constrained embedded systems

这是它的源代码:http ://code.google.com/p/protothread/

顺便说一句,在Contiki嵌入式操作系统中也使用protothread,用于在Contiki中实现进程。

于 2012-12-05T07:55:09.023 回答