//-------- scribble.c (for Archos 605) (c)2010 JG ---------- #include #include #include #include #include #include #define NUMCOLOURS 16 // RGB565 pixel format is used for archos display // COLOURS = { yellow, orange, red, dark red, green, dark green, blue , dark blue, // magenta, dark magenta, brown, cyan, white, light grey, dark gey, black } unsigned short COLOURS[NUMCOLOURS] = { (31<<11)|(63<<5), (31<<11)|(32<<5), 31<<11, 20<<11, \ 63<<5, 33<<5, 31, 17, (31<<11)|31, (16<<11)|16, (25<<11)|(38<<5)|12, (63<<5)|31, 0xffff, \ (22<<11)|(22<<6)|22, (12<<11)|(12<<6)|12, 0 }; // archos screen is 800x480, 16bit (2 bytes) per pixel, so use short int pointer int XRES = 800, YRES = 480; unsigned short int *fbp = 0; // framebuffer pointer used for direct access // file descriptors for framebuffer, touchscreen and tty0 int fbfd=0, tsfd=0, ttyfd=0; struct ts_event tsev; struct ts_event { unsigned short pressure; unsigned short x; unsigned short y; unsigned short millisecs; }; // these values will be reset by the calibration routine int min_x=100, max_x=4000, min_y=100, max_y=4000; int margin = 30; int palette_y = 20 , offset=50; // height & starting position of palette selection squares int maxbrush = 30; // must not be bigger than margin int brushsize = 12; // initial brushsize in pixels int colour = 6; // initial pen colour is blue int bkgcolour = 12; // background colour is white #define CIRCLE 0 #define SQUARE 1 int brushtype = CIRCLE; // initial brushtype int ts_toggle = 0; // prevents certain successive touchscreen events until stylus is lifted from the screen int palette_toggle = 0; // set to 1 when palette selection screen is shown int xpos, ypos; int oldx = -1, oldy = -1; unsigned char key = 0; // data storage for the picture elements // data[n][0] >= 0 for oldx coordinate // data[n][0] = -1 when sylus was removed from screen (start of new line) // data[n][0] = -99 when clear screen executed #define MAXDATA 9999 short int data[MAXDATA][7]; int datapos = 0, enddatapos = 0; // forward declarations void draw_rec(int x, int y, int sidex, int sidey, unsigned short clr); void draw_circle(int x, int y, int radius, unsigned short clr); void setPixel(int x, int y, unsigned short clr); void draw_line(int x1, int y1, int x2, int y2, int thickness, unsigned short clr); void calibrate_screen(); void calibrate_from_line( int x1, int y1, int x2, int y2 ); void init_margin(); void draw_brush_status(); void redraw_image(); void display_palette(); int main() { // variables used by the select() command to poll touchscreen & keyboard simultaneously fd_set rfds; struct timeval tv; int maxfd, retval=0; // set uid, since running from setuid only sets euid setuid(0); // Open the framebuffer for reading and writing fbfd = open("/dev/fb0", O_RDWR); if (!fbfd) { printf("Error: cannot open framebuffer device.\n"); exit(1); } // Map the framebuffer to memory fbp = (unsigned short int *)mmap(0, XRES*YRES*2, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0); if (!fbp) { printf("Cannot map framebuffer memory.\n"); exit(1); } // Open the touchscreen for reading tsfd = open("/dev/input/tsraw0", O_RDONLY) ; if (!tsfd) { printf("Error: cannot open touchscreen raw device.\n"); exit(1); } // Open /dev/tty0 for reading key press events ttyfd = open("/dev/tty0", O_RDONLY); if (!ttyfd) { printf("Error: cannot open /dev/tty0.\n"); exit(1); } calibrate_screen(); // clear the screen draw_rec(0,0,XRES,YRES,COLOURS[bkgcolour]); // seed the random number generator for repeatable airbrush strokes :) srand(1); // load any previously dumped screen system("cp /mnt/data/scribble.raw /dev/fb0"); // redraw the palette squares and brush status init_margin(); // process touchscreen events and check for key_presses do { /* Watch touchscreen and tty0 (keys) for when they have input. */ FD_ZERO(&rfds); FD_SET(tsfd, &rfds); FD_SET(ttyfd, &rfds); /* Set wait period to 10 millisecs */ tv.tv_sec = 0; tv.tv_usec = 10; maxfd = ttyfd+1; if (tsfd > ttyfd) maxfd = tsfd+1; retval = select(maxfd, &rfds, NULL, NULL, &tv); if (retval) { if (FD_ISSET(tsfd, &rfds)) // was there a touchscreen event? { read(tsfd, &tsev, 8); //printf("pressure=%u x=%u y=%u msec=%u\n", tsev.pressure, tsev.x, tsev.y, tsev.millisecs); // if stylus removed, reset oldx for start of new line if (tsev.pressure == 0) { oldx = -1; ts_toggle = 0; continue; } xpos = ((tsev.x-min_x) * XRES) / (max_x-min_x); ypos = ((tsev.y-min_y) * YRES) / (max_y-min_y); //check for events on rhs of screen //don't process them if we are currently drawing a line (oldx >= 0) if (xpos>XRES-margin && oldx < 0) { if (ypos < margin) { // reset/clear screen if (ts_toggle == 1) continue; draw_rec(0,0,XRES,YRES,COLOURS[bkgcolour]); init_margin(); data[datapos][0] = -99; // mark data with clear screen event srand(1); // reseed random numbers oldx=-1; datapos++; enddatapos=datapos; ts_toggle = 1; continue; } else if (ypos < NUMCOLOURS*palette_y+offset && ypos >= offset) colour = (ypos-offset)/palette_y; // select colour from tapped palette square else if (ypos > YRES-margin*2 && ypos < YRES-margin) { brushsize++; if (brushsize>maxbrush) brushsize=1; } else if (ypos > YRES-margin && ts_toggle == 0) { brushtype = 1 - brushtype; // toggle circle/square ts_toggle = 1; // set this so we don't keep changing brushtype while stylus remains in contact with screen } draw_brush_status(); oldx = -1; continue; } else // event happened in drawing region { // check we are not drawing off-screen if (brushtype == SQUARE) { if (xpos<0) xpos=0; if (ypos<0) ypos=0; if (ypos>YRES-brushsize) ypos=YRES-brushsize; } else { // brushtype = CIRCLE if (xpos<(brushsize>>1)) xpos=brushsize>>1; if (ypos<(brushsize>>1)) ypos=brushsize>>1; if (ypos>YRES-(brushsize>>1)) ypos=YRES-(brushsize>>1); } // don't draw on the far rhs (over the palette squares) if (brushtype == SQUARE && xpos>XRES-margin-brushsize) { xpos=XRES-margin-brushsize; } else if (brushtype == CIRCLE && xpos>XRES-margin-(brushsize>>1)) { xpos=XRES-margin-(brushsize>>1); } if (oldx < 0) // start a new line { if (brushtype == SQUARE) draw_rec(xpos, ypos, brushsize, brushsize, COLOURS[colour]); else draw_circle(xpos, ypos, brushsize>>1, COLOURS[colour]); } else // we continue to draw the current line { draw_line(oldx, oldy, xpos, ypos, brushsize, COLOURS[colour]); } // store image data if (datapos < MAXDATA) { data[datapos][0] = oldx; data[datapos][1] = oldy; data[datapos][2] = xpos; data[datapos][3] = ypos; data[datapos][4] = colour; data[datapos][5] = brushtype; data[datapos][6] = brushsize; datapos++; enddatapos=datapos; } oldx = xpos; oldy = ypos; } } if (FD_ISSET(ttyfd, &rfds)) // was a key pressed? { // decode the key press event read(ttyfd, &key, 1); if (key == 0x3c) break; // X key, so exit switch (key) { case 0x72: brushsize--; break; // Volume Down case 0x73: brushsize++; break; // Volume Up case 0x67: colour--; break; // Up case 0x6c: colour++; break; // Down case 0x3b: brushtype = 1-brushtype; break; // Menu right } if (brushsize < 1) brushsize=1; if (brushsize > maxbrush) brushsize=maxbrush; if (colour < 0) colour = NUMCOLOURS-1; if (colour == NUMCOLOURS) colour = 0; draw_brush_status(); if (key == 0x1c) { // OK button pressed - so save image, takes 4-5 secs draw_rec(XRES-margin,0,margin,YRES,COLOURS[bkgcolour]); // clear margin before saving pic system("/mnt/data/homebrew/fbgrab -w 800 -h 480 -b 16 /mnt/data/Pictures/scribble-`date +%F-%H%M%S`.png"); init_margin(); // redraw margin area } if (key == 0x3e) { // Left menu = palette selection screen toggle if (palette_toggle == 0) { display_palette(); palette_toggle = 1; } else { redraw_image(); palette_toggle = 0; } } if (key == 0x6d && datapos > 0) { // Left Down = Undo datapos--; // go back to the end of last drawn element or undo clear screen if ( data[datapos][0] == -99 ) { if (datapos > 0) datapos--; } else while ( data[datapos][0] >= 0 && (datapos > 0) ) datapos--; redraw_image(); } else if (key == 0x68 && datapos < enddatapos) { // left UP = Redo datapos++; if ( data[datapos][0] != -99 ) { // go forward until line end or clear screen while ( data[datapos][0] >= 0 && (datapos < enddatapos) ) datapos++; if ( data[datapos][0] == -99 ) datapos--; } redraw_image(); if (data[datapos][0] == -99) { datapos++; srand(1); } } // Vol Left/Right = fine Redo/Undo if (key == 0x69 && datapos > 0) { datapos--; redraw_image(); } else if (key == 0x6a && datapos < enddatapos) { datapos++; redraw_image(); } // TV/LCD key if (key == 0x3d) { bkgcolour = colour; redraw_image(); } } } } while (1); // dump the screen char shellstr[256]; sprintf(shellstr, "dd if=/dev/fb0 of=/mnt/data/scribble.raw bs=%u count=%u", XRES*2, YRES); system(shellstr); //blank (grey) the screen before exit draw_rec(0,0,XRES,YRES,(24<<11)|(24<<6)|24); munmap(fbp, XRES*YRES*2); close(fbfd); close(tsfd); close(ttyfd); return 0; } void draw_rec(int x, int y, int sidex, int sidey, unsigned short clr) { unsigned short int *pos; int i,j; pos = fbp + x + y*XRES; for (j=0; j r2) r--; // draw horiz line above the centre pos = fbp + (x-r) + (y-a)*XRES; for (b=0; b<2*r; b++) { /*if ((rand()&3) == 0)*/ *(pos++) = clr; } // draw horiz line below centre pos = fbp + (x-r) + (y+a)*XRES; for (b=0; b<2*r; b++) { /*if ((rand()&3) == 0)*/ *(pos++) = clr; } } } void draw_line(int x1, int y1, int x2, int y2, int thickness, unsigned short clr) { // Bresenham algorithm. int i,dx,dy,sdx,sdy,dxabs,dyabs,x,y,px,py; dx=x2-x1; /* the horizontal distance of the line */ dy=y2-y1; /* the vertical distance of the line */ dxabs=abs(dx); dyabs=abs(dy); sdx= dx >= 0 ? 1 : -1; sdy= dy >= 0 ? 1 : -1; x=dyabs>>1; y=dxabs>>1; px=x1; py=y1; if (dxabs>=dyabs) /* the line is more horizontal than vertical */ { for(i=0;i=dxabs) { y-=dxabs; py+=sdy; } px+=sdx; if (brushtype == SQUARE) draw_rec(px,py,thickness,thickness,clr); else /*if ((rand()&3) == 0)*/ draw_circle(px,py,thickness>>1,clr); //setPixel(px,py,clr); } } else /* the line is more vertical than horizontal */ { for(i=0;i=dyabs) { x-=dyabs; px+=sdx; } py+=sdy; if (brushtype == SQUARE) draw_rec(px,py,thickness,thickness,clr); else /*if ((rand()&3) == 0)*/ draw_circle(px,py,thickness>>1,clr); //setPixel(px,py,clr); } } } int cal_x=0, cal_y=0; void calibrate_screen() { FILE *fp = fopen("/mnt/data/scrib_calibration.txt","r"); if (fp) { // read previous saved calibration fscanf(fp, "%d %d %d %d\n", &min_x, &max_x, &min_y, &max_y); fclose(fp); return; } // otherwise calibrate manually // clear to a black screen draw_rec(0,0,XRES,YRES,0); int oldbrushtype = brushtype; brushtype = SQUARE; // screen left, min_x >= 0; calibrate_from_line(0,120,0,360); min_x = cal_x; // screen bottom, max_y <= 4096 calibrate_from_line(200,475,600,475); max_y = cal_y; // screen right, max_x <= 4096 calibrate_from_line(795,120,795,360); max_x = cal_x; // screen top, min_y >= 0 calibrate_from_line(200,0,600,0); min_y = cal_y; brushtype = oldbrushtype; // save calibration fp = fopen("/mnt/data/scrib_calibration.txt", "w"); if (fp) { fprintf(fp, "%d %d %d %d\n", min_x, max_x, min_y, max_y); fclose(fp); } //printf("min_x = %u , max_x = %u, min_y = %u, max_y = %u\n", min_x, max_x, min_y, max_y); } void calibrate_from_line( int x1, int y1, int x2, int y2 ) { draw_line(x1,y1,x2,y2,5,31<<11); while (1) { read(tsfd, &tsev, 8); if (tsev.pressure == 0) break; cal_x = tsev.x; cal_y = tsev.y; } draw_line(x1,y1,x2,y2,5,0); } void init_margin() { //clear the margin draw_rec(XRES-margin,0,margin,YRES,0xffff); //Draw the "clear screen" square draw_rec(XRES-margin,0,margin,margin,0); draw_rec(XRES-margin+2,2,margin-4,margin-4,COLOURS[bkgcolour]); //Draw the palette squares int c=0; for (; c>1; if (brushtype == SQUARE) draw_rec(XRES-margin+delta, YRES-margin*2+delta, brushsize, brushsize, 0); else draw_circle(XRES-(margin/2),YRES-(margin*3/2),brushsize>>1, 0); // display brush brushtype & colour in bottom rh corner if (brushtype == SQUARE) draw_rec(XRES-margin, YRES-margin, margin, margin, COLOURS[colour]); else draw_circle(XRES-(margin/2), YRES-(margin/2), margin/2, COLOURS[colour]); } // redraw image from image element data void redraw_image() { int p = datapos; int savecolour = colour; int savesize = brushsize; int savebrushtype = brushtype; if (p == 0) // load previosly dumped screen system("cp /mnt/data/scribble.raw /dev/fb0"); else { // find last clearscreen event or go back to beginning while (p > 0 && data[p][0] != -99) p--; if (p == 0 && data [p][0] != -99) // load previously dumped screen system("cp /mnt/data/scribble.raw /dev/fb0"); else { // clear screen draw_rec(0,0,XRES-margin,YRES,COLOURS[bkgcolour]); p++; } } // redraw the palette squares and brush status init_margin(); // reseed random number generator so we get the same air brush strokes srand(1); while (p < datapos) { oldx = data[p][0]; oldy = data[p][1]; xpos = data[p][2]; ypos = data[p][3]; colour = data[p][4]; brushtype = data[p][5]; brushsize = data[p][6]; if (oldx < 0) // start a new line { if (brushtype == SQUARE) draw_rec(xpos, ypos, brushsize, brushsize, COLOURS[colour]); else draw_circle(xpos, ypos, brushsize>>1, COLOURS[colour]); } else // we continue to draw the current line { draw_line(oldx, oldy, xpos, ypos, brushsize, COLOURS[colour]); } p++; } colour = savecolour; brushsize = savesize; brushtype = savebrushtype; draw_brush_status(); oldx = -1; } void display_palette() { // clear screen draw_rec(0,0,XRES,YRES,COLOURS[bkgcolour]); int xo=10, yo=-54, x, y; for (y=32; y<=256; y++) { int r=31, g=0, b=0; for (x=0; x<=256; x++) { // add darkness gradient as y varies int rr = (r*y)>>8; int gg = (g*y)>>8; int bb = (b*y)>>8; draw_rec(xo+(x<<1),yo+(y<<1),2,2,(rr<<11)|(gg<<5)|bb); if (x<64) g=x; else if (x<96) r=95-x; else if (x<128) b=x-96; else if (x<192) g=191-x; else if (x<224) r=x-192; else b=255-x; } } // draw greyscale for (y=0; y<64; y++) { draw_rec(540, 10+(y*7), 64, 7, ((y>>1)<<11)|(y<<5)|(y>>1)); } }