789 views
in System Integration by

Hi All,

 Posting this incase someone else is looking to read in PNG files off a ChaN_FatFS file system. The LibPNG codebase I sourced from SEGGER and modified to support FatFS is here: http://s000.tinyupload.com/?file_id=49354806145704772533

EwLoadExternBitmap() is as follows. 

Hope this saves someone time.
 


#define PNG_BYTES_TO_CHECK 8

/*******************************************************************************
* FUNCTION:
*   EwLoadExternBitmap
*
* DESCRIPTION: wThis function has the job to create and fill a bitmap with content
*  of an extern image file. aName identified extern file to load. If successful,
*  an initialized bitmap is returned otherwise NULL.
*******************************************************************************/
XBitmap* EwLoadExternBitmap( XString aName )
{
  // LibPNG
  png_structp 					png_ptr;
  png_infop 					info_ptr;
  unsigned char					png_header[PNG_BYTES_TO_CHECK];
  uint8_t						channels, color_type;

  // FILE IO
  static FIL                    fil;
  FRESULT 						fr;

  // EW
  XBitmap*                      bitmap  = 0;
  XBitmapLock*                  lock    = 0;
  int                           nameLen = EwGetStringLength( aName );
  char*                         name    = malloc( nameLen + 1 );
  XPoint                        frameSize;
  XRect                         lockArea;

  /* Out of memory? */
  if ( !name )
    return NULL;

  /* Convert the 16-bit wide char string in an 8-bit ANSI string */
  EwStringToAnsi( aName, name, nameLen + 1, 0 );

  // Try to open the file
  fr = f_open(&fil, name, FA_READ);
  if(fr)
  {
	  PRINTF("EwLoadExternBitmap() f_open() error\n\r");
	  free( name );
	  return NULL;
  }

  // read PNG header
  f_read(&fil, png_header, PNG_BYTES_TO_CHECK, NULL);
  if (png_sig_cmp(png_header, 0, PNG_BYTES_TO_CHECK))
  {
	  PRINTF("File %s is not recognized as a PNG file\n\r", name);
	  free(name);
	  f_close(&fil);
	  return NULL;
  }

  // initialize stuff
  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if (!png_ptr)
  {
	  PRINTF("png_create_read_struct() failed\n\r");
	  free(name);
	  f_close(&fil);
	  return NULL;
  }

  info_ptr = png_create_info_struct(png_ptr);
  if (!info_ptr)
  {
	  PRINTF("png_create_info_struct() failed\n\r");
	  png_destroy_read_struct(&png_ptr, NULL, NULL);
      free(name);
      f_close(&fil);
      return NULL;
  }

  png_init_io(png_ptr, &fil);
  png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK);
  png_read_info(png_ptr, info_ptr);
  png_set_scale_16(png_ptr);

  color_type = png_get_color_type(png_ptr, info_ptr);
  if (color_type == PNG_COLOR_TYPE_PALETTE)
     png_set_palette_to_rgb(png_ptr);

  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) != 0)
       png_set_tRNS_to_alpha(png_ptr);

  // Get PNG file info
  png_read_update_info(png_ptr, info_ptr);
  channels = png_get_channels(png_ptr, info_ptr);
  frameSize.X = png_get_image_width(png_ptr, info_ptr);
  frameSize.Y = png_get_image_height(png_ptr, info_ptr);
  PRINTF("[SYSTEM EVENT] Opened:%s %ix%i Channels:%i ColorType:%i   \n\r", name, frameSize.X, frameSize.Y, channels, color_type);

  /* Create an Embedded Wizard bitmap to store the image pixel data */
  bitmap      = EwCreateBitmap( EW_PIXEL_FORMAT_NATIVE, frameSize, 0, 1 );

  /* Could create the image */
  if ( bitmap )
  {
    lockArea.Point1.X = 0;
    lockArea.Point1.Y = 0;
    lockArea.Point2.X = frameSize.X;
    lockArea.Point2.Y = frameSize.Y;

    lock = EwLockBitmap( bitmap, 0, lockArea, 0, 1 );

    png_bytep row_pointer;
    row_pointer = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));

    for (uint32_t pass = 0; pass < frameSize.Y; pass++)
    {
    	png_read_rows(png_ptr, &row_pointer, NULL, 1);

        unsigned int*  dest   = (unsigned int*)((char*)lock->Pixel1 + pass * lock->Pitch1Y );

    	for (uint32_t x = 0; x < frameSize.X; x++)
    	{
    		png_byte* ptr = &(row_pointer[x*channels]);

    		// Get the RGBA values from the PNG decoder and arrange the 'red', 'green', 'blue' channels at the right bit offset position
    		unsigned int red   = ptr[0] << EW_COLOR_CHANNEL_BIT_OFFSET_RED;
    		unsigned int green = ptr[1] << EW_COLOR_CHANNEL_BIT_OFFSET_GREEN;
    		unsigned int blue  = ptr[2] << EW_COLOR_CHANNEL_BIT_OFFSET_BLUE;
    		unsigned int alpha;
    		if(channels == 4)
    			alpha = ptr[3] << EW_COLOR_CHANNEL_BIT_OFFSET_ALPHA;
    		else
    			alpha = 0xFF << EW_COLOR_CHANNEL_BIT_OFFSET_ALPHA;

    		// Store the pixel data in the bitmap memory
    		*dest++ = red | green | blue | alpha;
    	}

    }

    png_free(png_ptr, row_pointer);
    row_pointer = NULL;
    png_read_end(png_ptr, info_ptr);
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

    /* Don't forget to unlock the bitmap */
    EwUnlockBitmap( lock );
  }

  /* After finish_decompress, we can close the input file. */
  f_close(&fil);

  /* Release temporary memory */
  free( name );

  /* all ok */
  return bitmap;
}

 

1 Answer

0 votes
by
Hello Mike,

thank you very much!

Best regards

Paul Banach
by
Hey Paul,

 Looping back on this.. would like to add multi-frame support and read EW's online docs. Is there a reference bit of code on how to actually do this? The docs show:

  bitmap      = EwCreateBitmap( EW_PIXEL_FORMAT_NATIVE, frameSize, 0, 6 );

...for doing 6 frames. But it's unclear to me how you determine a) the number of frames the function needs to deal with b) how pipe that into EwLoadExternBitmap()   ... ???
by

Hello Mike,

the number of frames depends on your application case. In practice, we use multi-frame bitmaps only when we need a bitmap with more than one image so that the application can thereupon select (switch) the images (the frames) dynamically at the runtime or even display multiple frames simultaneously. Please see the sections: Multi-frame and animated bitmaps and e.g. Select the frame number of a multi-frame bitmap resource for more details about the multi-frame functionality. Without knowing your application case, it is difficult to give you an indication for the number of frames the functions needs to deal with. It's finally up to you.

In our documentation we showed how to create a bitmap with 6 frames. It is just an example. In this case the function EwLoadExternBitmap() creates one bitmap with 6 frames and then in a for-loop it locks, loads with contents and unlocks each frame individually. This is in fact the same implementation as when you have a bitmap with one frame. The difference is, you repeat the lock/load/unlock operations to also fill all other frames belonging to the bitmap.

Generally, the function EwLoadExternBitmap() has to create and load all frames at once. The returned bitmap contains thus all the contents already. Consequently, the included bitmap frames also occupy memory at the same time. This may be important if RAM is scarce resource in your target and you intend to deal with large bitmaps and many frames.

Does it help you?

Best regards

Paul Banach

by
Hi Paul,

 Yes, I had looked at that documentation you referenced. With the number of frames hard coded to 6. And the only variable passed in is Filename: XBitmap* EwLoadExternBitmap( XString aName ). Or is EwLoadExternBitmap() to derive the frame count on its own. And then ExternBitmap.load{} to pull the frame count out via the handle?

So I'm curious if anyone has implemented  EwLoadExternBitmap() in such a way to handle whatever number of frames a Chora image resource might require.

Mike
by

Hello Mike,

Or is EwLoadExternBitmap() to derive the frame count on its own. And then ExternBitmap.load{} to pull the frame count out via the handle?

Exact. Depending on your application case and the image content EwLoadExternBitmap() can create multi-frame bitmap or even not. It can even create an animated bitmap. Assuming you have enough RAM then EwLoadExternBitmap() could decode a short video sequence and store it in a multi-frame bitmap. The GUI application can play such animation thereupon. In this case it is the job of EwLoadExternBitmap() to decide how many frames should the resulting bitmap have and how large a single frame is.

Alternative approach, you pass in the aName function parameter some further information for EwLoadExternBitmap(). For this purpose you encode all the information in the aName string. How you do this is up to you. For example: if you want to pass in aName the name of some image file AND the size of a single frame, you could encode this information in the string as follows: "Image123.png,100x200". In the implementation of EwLoadExternBitmap() you split the aName string in the components name, frame-width and frame-height. Using the component name you open the desired file. The components frame-width and frame-height you decode to integer numbers. Knowing the frame size your implementation of the EwLoadExternBitmap() can now divide the image in frames and create a multi-frame bitmap with the matching number of frames.

So I'm curious if anyone has implemented  EwLoadExternBitmap() in such a way to handle whatever number of frames a Chora image resource might require.

For test purpose we implemented such version in the past. This corresponds to the found in our documentation. In  the practice I really don't know such application cases.

Does it help you?

Best regards

Paul Banach

by
Thanks Paul,

 Well... I'm still a bit confused because it's the ExternBitmap resource that has the NoOfFrames property. So does it not dictate how many frames need to be processed? Moreover, ExternBitmap may reference a 1,2 or 6 frame bitmap. So either EwLoadExternBitmap() has to dynamically figure out how many frames are in the image and pass this up, or be told from above how many via NoOfFrames somehow. No?  Or are both approaches valid? If its entirely on EwLoadExternBitmap() to determine frame count how does one dynamically figure that out from a png? Is there some byte in the png to specify it?

Mike
by

Hello Mike,

the property NoOfFrames in case of the Resources::ExternBitmap class is read-only. It provides a technique to query the number of frames available in the bitmap. If EwLoadExternBitmap() function created and returned a bitmap with 5 frames, the property  NoOfFrames will contain the value 5. The GUI application can thereupon evaluate this value and calculate with the number of frames.

or be told from above how many via NoOfFrames somehow. No?

Not via NoOfFrames. It is read-only and can't be changed by the application. If you have an application case where it is necessary that the GUI dictates EwLoadExternBitmap() function how many frames should the returned bitmap contain, then you have to encode this information in the Name property, which is passed 1:1 in the aName parameter. I have explained this in the comment above.

So either EwLoadExternBitmap() has to dynamically figure out how many frames are in the image and pass this up, ...

Exact. EwLoadExternBitmap() function creates a bitmap with e.g. 5 frames. This information is stored internally in the returned bitmap. The Resources::ExternBitmap class reads this information from the bitmap and initializes the property NoOfFrames. By the way it also initializes the properties FrameSize and FrameDelay. All these properties are ready-only and they simply reflect the attributes of the bitmap.

If its entirely on EwLoadExternBitmap() to determine frame count how does one dynamically figure that out from a png? Is there some byte in the png to specify it?

PNG does not know such multi-frame concept. It is in fact a detail of your concrete application. If you need multi-frame bitmaps, then you have to decide how to handle this. One option would be: you consider one large PNG file as being composed (virtually) from smaller frames arranged side by side in rows and columns. In such case you need to know the size of a single frame. Depending on your application case this frame size can be fixed for all PNGs or it can differ for each PNG individually. In the last case you will need  to store the individual frame size in some 'attribute' files accompanying the PNG files. EwLoadExternBitmap() function can read the frame size from the 'attributes' file and split the corresponding PNG file in frames when creating the bitmap. Or, if this is required in your application case, you can pass the desired frame size directly in the aName parameter as explained in my preceding comment.

The usage of frames is just on option. Do you have a concrete application case in mind?

Best regards

Paul

by

The usage of frames is just on option. Do you have a concrete application case in mind?''

Well, I'm looking to stop storing images for widgets like FlatButtonFrame.png, Track.png, PushButtonMedium.png etc in flash and to load off SD card. Each of these that you've provided has a different frame count, size and direction. Surely I cant be expected to write a EwLoadExternBitmap() for each one and have a unique corresponding ::ExternBitmap for each???? How can I write EwLoadExternBitmap() in such a way so as to be able to handle all the various multiframe images EW has provided?

by

Hello Mike,

interesting in this context is the motivation why do you want to stop storing images in the flash? I assume following two reasons:

Reason 1: Insufficient flash size

Reason 2: Exchange images in the target system

If the first case is true, can you provide us more details concerning your HW? How much flash is available? Anyway, there are two configuration techniques to save flash:

1. Configure the bitmaps to be stored in Compressed format.

2. Configure the pixel format how bitmap data is stored. For example, Index8 format can be up to 4 times more efficient than Native format. Please note: the possibility to configure the format has been added in Embedded Wizard 9.20.

In case your motivation is the requirement to exchange the image files in the target system (for example, you want your customer to be able to customize some images, etc.), then the here in this thread discussed Extern Bitmap is the right approach. In this case I would:

1. On your SD Card store all the image files.

2. The SD card should also manage for each file additional attribute information: FrameSize and FrameDelay. This allows your customer to not only change the image contents but also their size, etc.

3. In the GUI application you limit to specify the name of the desired image file, e.g. "WelcomeLogo".

4. The implementation of the EwLoadExternBitmap() function searches on SD card for the corresponding image file. Similarly the function also searches in the attributes associated to the image file for the frame size and frame delay values. Concrete, the function does:

a. Read the attributes associated to the image file (frame size, frame delay)

b. Open and load the image file by using e.g. libPNG decoder, etc.

c. Query the actual size of the image (width, height)

d. Calculate the number of frames contained in the image. Assuming, the frames are stored in columns and rows side-by-side:

int cols = image_width  / frame_width;
int rows = image_height / frame_height;

int frames = cols * rows;

e. Create an Embedded Wizard Bitmap with the previously estimated frame size (step a) and the calculated number of frames (step d).

f. For each frame lock the Embedded Wizard bitmap and copy there the corresponding frame area from the image file. Assuming in the image file the frames are arranged in columns and rows, the first frame (number 0) could be found at the top left corner of the image. The next frame on its right. If all frames within the  row are processed the next frame is found at the left edge of the next row. This is the usual arrangement. In fact, however, it is up to you how you manage the frames. You can even store the frames in separate files within sub-directories. etc. 

This approach avoids the necessity of providing any attribute information via Extern Bitmap interface. In your GUI application you limit to specify the desired image e.g. "WelcomeLogo". How large the image is, whether it does contain multiple frames and how many frames they are, it is a detail which can be changed when the images are replaced on the SD card. For example the welcome logo could be replaced by a short animation sequence composed of multiple frames. Therefore, I would manage the image attributes together with the image files on the SD card.

You could manage for this purpose an attribute file where for each image file you store the associated attributes. Or, more pragmatic, you can encode the attributes directly in the name of the image file. For example, the following file name could be interpreted as composed of four parts: the name of the image as it will be addressed from the GUI application (WelcomeLogo), the frame width (320), frame height (240) and the frame delay in milliseconds (50):

WelcomeLogo-320-240-50.png

The GUI application asks for the image named WelcomeLogo. The implementation of EwLoadExternBitmap() searches the SD card for the file starting with WelcomeLogo- and once known its complete name, decodes from the name the attributes for the frame width, frame height and frame delay. When you exchange the image files later you only need to adapt the attributes directly in the image file name. This approach is just a suggestion and you can implement any other kind of attribute management.

I hope this explanation helps you further. In order to provide you better indication it could help us to better know your exact motivation for not to store the image files in flash.

Best regards

Paul Banach

Ask Embedded Wizard

Welcome to the question and answer site for Embedded Wizard users and UI developers.

Ask your question and receive answers from the Embedded Wizard support team or from other members of the community!

Embedded Wizard Website | Privacy Policy | Imprint

...