android native screen capture tool screen It is possible to write a native C application, that opens the framebuffer (/dev/graphics/fb0) to extract bitmap data representing the screen surface image.

It is also possible to modify the framebuffer data and by doing so to do a low level drawing on screen (that is also very fast).

The data must be then converted (aligned/reversed) to standard BMP format, and saved to disc.

The framebuffer grabber:

  1.  
  2. static int get_framebuffer(GGLSurface *fb)
  3. {
  4. int fd;
  5. void *bits;
  6.  
  7. fd = open("/dev/graphics/fb0", O_RDWR);
  8. if(fd < 0) {
  9. perror("cannot open fb0");
  10. return -1;
  11. }
  12.  
  13. if(ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
  14. perror("failed to get fb0 info");
  15. return -1;
  16. }
  17.  
  18. if(ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
  19. perror("failed to get fb0 info");
  20. return -1;
  21. }
  22.  
  23. //dumpinfo(&fi, &vi);
  24.  
  25. bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  26. if(bits == MAP_FAILED) {
  27. perror("failed to mmap framebuffer");
  28. return -1;
  29. }
  30.  
  31. fb->version = sizeof(*fb);
  32. fb->width = vi.xres;
  33. fb->height = vi.yres;
  34. fb->stride = fi.line_length / (vi.bits_per_pixel >> 3);
  35. fb->data = bits;
  36. fb->format = GGL_PIXEL_FORMAT_RGB_565;
  37.  
  38. fb++;
  39.  
  40. fb->version = sizeof(*fb);
  41. fb->width = vi.xres;
  42. fb->height = vi.yres;
  43. fb->stride = fi.line_length / (vi.bits_per_pixel >> 3);
  44. fb->data = (void*) (((unsigned) bits) + vi.yres * vi.xres * 2);
  45. fb->format = GGL_PIXEL_FORMAT_RGB_565;
  46.  
  47. return fd;
  48. }
  49.  

The BITMAP former:
The first thing to do is to convert the Framebuffer's 16 bit data into 24 bit. We're not gaining extra-quality here, we're just "stretching" the colors from:
5 bits blue : 2^5=32 blue color nuances
6 bits green : 2^6=64 green color nuances (in 16bit colors, green has more color space since the human eye is more sensitive to green color)
5 bits red : 2^5=32 red color nuances
32x64x32 possible 16bits colors (2^16)
To:
8 bits blue : 2^8 = 256 color nuances
8 bits green : 2^8 = 256 color nuances
8 bits red : 2^8 = 256 color nuances
A total of 2^24 colors, but as I said we're just "stretching" the color intervals without any information gain.

  1.  
  2. //convert pixel data
  3. uint8_t *rgb24;
  4. if (depth == 16)
  5. {
  6. rgb24 = (uint8_t *)malloc(w * h * 3);
  7. int i = 0;
  8. for (;i<w*h;i++)
  9. {
  10. uint16_t pixel16 = ((uint16_t *)gr_framebuffer[0].data)[i];
  11. // RRRRRGGGGGGBBBBBB -> RRRRRRRRGGGGGGGGBBBBBBBB
  12. // in rgb24 color max is 2^8 per channel (*255/32 *255/64 *255/32)
  13. rgb24[3*i+2] = (255*(pixel16 & 0x001F))/ 32; //Blue
  14. rgb24[3*i+1] = (255*((pixel16 & 0x07E0) >> 5))/64; //Green
  15. rgb24[3*i] = (255*((pixel16 & 0xF800) >> 11))/32; //Red
  16. }
  17. }
  18. else
  19. if (depth == 24)
  20. {
  21. rgb24 = (uint8_t *) gr_framebuffer[0].data;
  22. }
  23. else
  24. {
  25. //free
  26. close(gr_fb_fd);
  27. exit(2);
  28. };
  29. //save RGB 24 Bitmap
  30. int bytes_per_pixel = 3;
  31. BMPHEAD bh;
  32. memset ((char *)&bh,0,sizeof(BMPHEAD)); // sets everything to 0
  33. //bh.filesize = calculated size of your file (see below)
  34. //bh.reserved = two zero bytes
  35. bh.headersize = 54L; // for 24 bit images
  36. bh.infoSize = 0x28L; // for 24 bit images
  37. bh.width = w; // width of image in pixels
  38. bh.depth = h; // height of image in pixels
  39. bh.biPlanes = 1; // for 24 bit images
  40. bh.bits = 8 * bytes_per_pixel; // for 24 bit images
  41. bh.biCompression = 0L; // no compression
  42. int bytesPerLine;
  43. bytesPerLine = w * bytes_per_pixel; // for 24 bit images
  44. //round up to a dword boundary
  45. if (bytesPerLine & 0x0003)
  46. {
  47. bytesPerLine |= 0x0003;
  48. ++bytesPerLine;
  49. }
  50. bh.filesize = bh.headersize + (long)bytesPerLine * bh.depth;
  51. FILE * bmpfile;
  52. //printf("Bytes per line : %d\n", bytesPerLine);
  53. bmpfile = fopen("screen.bmp", "wb");
  54. if (bmpfile == NULL)
  55. {
  56. close(gr_fb_fd);
  57. exit(3);
  58. }
  59. fwrite("BM",1,2,bmpfile);
  60. fwrite((char *)&bh, 1, sizeof (bh), bmpfile);
  61. //fwrite(rgb24,1,w*h*3,bmpfile);
  62. char *linebuf;
  63. linebuf = (char *) calloc(1, bytesPerLine);
  64. if (linebuf == NULL)
  65. {
  66. fclose(bmpfile);
  67. close(gr_fb_fd);
  68. exit(4);
  69. }
  70.  

Now the next thing to do is to align the BGR triplets and to switch order , to match the BMP RGB24 color format.

  1.  
  2. int line,x;
  3. for (line = h-1; line >= 0; line --)
  4. {
  5. // fill line linebuf with the image data for that line
  6. for( x =0 ; x < w; x++ )
  7. {
  8. *(linebuf+x*bytes_per_pixel) = *(rgb24 + (x+line*w)*bytes_per_pixel+2);
  9. *(linebuf+x*bytes_per_pixel+1) = *(rgb24 + (x+line*w)*bytes_per_pixel+1);
  10. *(linebuf+x*bytes_per_pixel+2) = *(rgb24 + (x+line*w)*bytes_per_pixel+0);
  11. }
  12. // remember that the order is BGR and if width is not a multiple
  13. // of 4 then the last few bytes may be unused
  14. fwrite(linebuf, 1, bytesPerLine, bmpfile);
  15. }
  16. fclose(bmpfile);
  17. close(gr_fb_fd);
  18.  

Download the code: (old code removed, see update below)
Note that you are not allowed to distribute this code without visibly indicating it's author (me) and the source (this page) and You may not embedded it in commercial applications.
To compile the code, use the NDK, as presented in this article.
Running the code produces a screen.bmp file:
android native screen capture tool

screen.bmp
android native screen capture tool screen

Hope this helps.

Update, April 03, 2012

By popular demand, I have modified the code to support RGB32 as well. In this case, we have an extra alpha channel, that we need to ignore when creating the final bitmap. So I have added a RGB32->RGB24 converter, but it is only a alpha channel remover, since we are not talking about true RGB32. The Red,Green,Blue channel are still 8bits each, as in the case of RGB24. Peter's solution below works, but I felt the need to write a simpler code:

  1.  
  2. if (depth == 32) //skip transparency channel
  3. {
  4. rgb24 = (uint8_t *) malloc(w * h * 3);
  5. int i=0;
  6. for (;i<w*h;i++)
  7. {
  8. uint32_t pixel32 = ((uint32_t *)gr_framebuffer[0].data)[i];
  9. // in rgb24 color max is 2^8 per channel
  10. rgb24[3*i+0] = pixel32 & 0x000000FF; //Blue
  11. rgb24[3*i+1] = (pixel32 & 0x0000FF00) >> 8; //Green
  12. rgb24[3*i+2] = (pixel32 & 0x00FF0000) >> 16; //Red
  13. }
  14. }
  15.  

Here is the complete source code, and the native ELF binary file:
Native
And a BMP result of my Android using an RGB32 screen:

When using capturescr ELF file in ADB shell, you can get the returned error code using:

  1.  
  2. echo $?
  3.  
(Visited 557 times, 13 visits today)
facebooktwittergoogle_plusredditpinterestlinkedinmailfacebooktwittergoogle_plusredditpinterestlinkedinmail
Tagged on: