/* * Argyll Color Correction System * ChromeCast Display target patch window * * Author: Graeme W. Gill * Date: 8/9/14 * * Copyright 2013, 2014 Graeme W. Gill * All rights reserved. * * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- * see the License.txt file for licencing details. */ #include #include #ifdef NT # include #endif #ifdef UNIX # include # include # include # include # ifdef __FreeBSD__ # include # endif /* __FreeBSD__ */ #endif #include "copyright.h" #include "aconfig.h" #include "icc.h" #include "numsup.h" #include "cgats.h" #include "conv.h" #include "dispwin.h" #include "conv.h" #include "mongoose.h" #include "ccast.h" #include "ccwin.h" #include "render.h" #undef WRITE_PNG /* [und] Write each test patch to "ccwin.png" */ #undef CCTEST_PATTERN /* [und] Create dev. spatial test pattern */ #undef SEND_TEST_FILE /* [und] Send this file name to ChromeCast instead of pattern */ #undef DO_TIMING /* [und] Print rendering timing */ #undef DEBUG /* [und] */ #define DDITHER 1 /* 0 = no dither - quantize to PNG 8 bit RGB value */ /* [def] 1 = use error diffusion dithering with ccast quant model */ /* 2 = use crafted 4x4 dither cell */ #define VWIDTH 1920.0 /* Video stream and display size */ #define VHEIGHT 1080.0 #define IWIDTH 1280.0 /* This is the native still image framebuffer size for ChromeCasts */ #define IHEIGHT 720.0 //#define STANDALONE_TEST #ifdef DEBUG #define errout stderr # define debug(xx) fprintf(errout, xx ) # define debug2(xx) fprintf xx # define debugr(xx) fprintf(errout, xx ) # define debugr2(xx) fprintf xx # define debugrr(xx) fprintf(errout, xx ) # define debugrr2(xx) fprintf xx # define debugrr2l(lev, xx) fprintf xx #else #define errout stderr # define debug(xx) # define debug2(xx) # define debugr(xx) if (p->ddebug) fprintf(errout, xx ) # define debugr2(xx) if (p->ddebug) fprintf xx # define debugrr(xx) if (callback_ddebug) fprintf(errout, xx ) # define debugrr2(xx) if (callback_ddebug) fprintf xx # define debugrr2l(lev, xx) if (callback_ddebug >= lev) fprintf xx #endif /* ================================================================== */ /* Chromwin context and (possible) web server */ typedef struct _chws { int verb; int ddebug; int direct; /* End PNG directly, rather than using web server */ struct mg_context *mg; /* Mongoose context (if needed) */ char *ws_url; /* Web server URL for accessing server */ // double hoff, voff; /* Input position of test square */ double x, y; /* position of test square in pixels */ double w, h; /* size of test square in pixels */ int pno; /* Index to generate a sequence of URLs */ unsigned char *ibuf; /* Memory image of .png file */ size_t ilen; ccast *cc; /* ChromeCast */ /* Update the png image */ int (*update)(struct _chws *p, unsigned char *ibuf, size_t ilen, double *bg); /* Destroy ourselves */ void (*del)(struct _chws *p); } chws; static void chws_del(chws *p) { if (p->mg != NULL) mg_stop(p->mg); if (p->cc != NULL) p->cc->del(p->cc); if (p->ibuf != NULL) free(p->ibuf); if (p->ws_url != NULL) free(p->ws_url); free(p); } /* Change the .png being served */ /* Return nz on error */ static int chws_update(chws *p, unsigned char *ibuf, size_t ilen, double *bg) { char url[200]; debug("\nUpdate png\n"); if (p->ibuf != NULL) free(p->ibuf); p->ibuf = ibuf; p->ilen = ilen; /* Send the PNG swatch direct */ if (p->direct) { double x, y, w, h; /* Convert x,y,w,h to relative rather than pixel size */ debugr2((errout,"Got x %f y %f w %f h %f\n", p->x, p->y, p->w, p->h)); // Convert from quantized to direct loader parameters if (p->w < IWIDTH) x = p->x/(IWIDTH - p->w); else x = 0.0; if (p->h < IHEIGHT) y = p->y/(IHEIGHT - p->h); else y = 0.0; w = p->w/(0.1 * IWIDTH); h = p->h/(0.1 * IWIDTH); debugr2((errout,"Sending direct x %f y %f w %f h %f\n", x, y, w, h)); if (p->cc->load(p->cc, NULL, p->ibuf, p->ilen, bg, x, y, w, h)) { debugr2((errout,"ccwin_update direct load failed\n")); return 1; } /* Using web server */ } else { #ifdef SEND_TEST_FILE sprintf(url, "%s%s",p->ws_url, SEND_TEST_FILE); #else sprintf(url, "%stpatch_%d.png",p->ws_url, ++p->pno); #endif if (p->cc->load(p->cc, url, NULL, 0, NULL, 0.0, 0.0, 0.0, 0.0)) { debugr2((errout,"ccwin_update server load failed\n")); return 1; } } return 0; } /* Web server event handler - return the current .png image */ static void *ccwin_ehandler(enum mg_event event, struct mg_connection *conn) { const struct mg_request_info *request_info = mg_get_request_info(conn); chws *p = (chws *)mg_get_user_data(conn); char *cp; char sbuf[200]; debugr2((errout,"ccwin_ehandler()\n")); if (event != MG_NEW_REQUEST) { return NULL; } debugr2((errout,"Event: uri = '%s'\n",request_info->uri)); #ifdef SEND_TEST_FILE #pragma message("############################# ccwin.c SEND_TEST_FILE defined ! ##") return NULL; #endif if (p->ibuf != NULL && p->ilen > 0 && (cp = strrchr(request_info->uri, '.')) != NULL && strcmp(cp, ".png") == 0) { debugr2((errout,"Event: Loading %s\n",request_info->uri)); debugr2((errout,"Returning current png size %d bytes\n",(int)p->ilen)); sprintf(sbuf, "HTTP/1.1 200 OK\r\n" "Content-Type: image/png\r\n" "Content-Length: %d\r\n" "\r\n" ,(int)p->ilen); mg_write(conn, sbuf, strlen(sbuf)); mg_write(conn, p->ibuf, p->ilen); } else { debugr2((errout,"Bad request or png - returning 404\n")); sprintf(sbuf, "HTTP/1.0 404 Not Found\r\n" "\r\n" "

Error 404 - Not Found

"); mg_write(conn, sbuf, strlen(sbuf)); } return "yes"; } chws *new_chws( ccast_id *cc_id, /* ChromeCast to open */ double width, double height, /* Width and height as % */ double hoff, double voff, /* Offset from center in fraction of screen, range -1.0 .. 1.0 */ int verb, int ddebug) { chws *p; const char *options[3]; char port[50]; int portno = 0; /* Port number allocated */ int forcedef = 0; /* Force default reciever app. */ if ((p = (chws *)calloc(sizeof(chws), 1)) == NULL) { error("new_chws: calloc failed"); return NULL; } p->verb = verb; p->ddebug = ddebug; p->update = chws_update; p->del = chws_del; /* We make sure we round the test patch size and */ /* location to integer resolution so that we can know */ /* it's exact relationship to the upsampled pixel locations. */ /* Setup window size and position */ /* The default size is 10% of the width */ p->w = floor(width/100.0 * 0.1 * IWIDTH + 0.5); if (p->w > IWIDTH) p->w = IWIDTH; p->h = floor(height/100.0 * 0.1 * IWIDTH + 0.5); if (p->h > IHEIGHT) p->h = IHEIGHT; // Make offset be on an even pixel boundary, so that we know // the up-filter phase. p->x = floor((hoff * 0.5 + 0.5) * (IWIDTH - p->w) + 0.5); p->y = floor((voff * 0.5 + 0.5) * (IHEIGHT - p->h) + 0.5); if (((int)p->x) & 1) p->x++; if (((int)p->y) & 1) p->y++; if (verb) printf("Opening ChromeCast '%s'\n",cc_id->name); #ifdef SEND_TEST_FILE forcedef = 1; #endif /* Connect to the chrome cast */ if ((p->cc = new_ccast(cc_id, forcedef)) == NULL) { error("new_ccast: failed"); chws_del(p); return NULL; } p->direct = p->cc->get_direct_send(p->cc); if (!p->direct) { /* Create a web server */ options[0] = "listening_ports"; // sprintf(port,"%d", 0); /* Use any available */ sprintf(port,"%d", 8081); /* Use fixed port for Linux firewall rule */ options[1] = port; options[2] = NULL; p->mg = mg_start(&ccwin_ehandler, (void *)p, options); if ((p->ws_url = mg_get_url(p->mg)) == NULL) { debugr2((errout, "mg_get_url() failed\n")); chws_del(p); return NULL; } if (p->ddebug) printf("Created .png server at '%s'\n",p->ws_url); } return p; } /* ================================================================== */ /* Get RAMDAC values. ->del() when finished. */ /* Return NULL if not possible */ static ramdac *ccwin_get_ramdac(dispwin *p) { debugr("webdisp doesn't have a RAMDAC\n"); return NULL; } /* Set the RAMDAC values. */ /* Return nz if not possible */ static int ccwin_set_ramdac(dispwin *p, ramdac *r, int persist) { debugr("webdisp doesn't have a RAMDAC\n"); return 1; } /* ----------------------------------------------- */ /* Install a display profile and make */ /* it the default for this display. */ /* Return nz if failed */ int ccwin_install_profile(dispwin *p, char *fname, ramdac *r, p_scope scope) { debugr("webdisp doesn't support installing profiles\n"); return 1; } /* Un-Install a display profile */ /* Return nz if failed, */ int ccwin_uninstall_profile(dispwin *p, char *fname, p_scope scope) { debugr("webdisp doesn't support uninstalling profiles\n"); return 1; } /* Get the currently installed display profile. */ /* Return NULL if failed. */ icmFile *ccwin_get_profile(dispwin *p, char *name, int mxlen) { debugr("webdisp doesn't support getting the current profile\n"); return NULL; } /* ----------------------------------------------- */ /* Change the window color. */ /* Return 1 on error, 2 on window being closed */ static int ccwin_set_color( dispwin *p, double r, double g, double b /* Color values 0.0 - 1.0 */ ) { chws *ws = (chws *)p->pcntx; int j; double orgb[3]; /* Previous RGB value */ double kr, kf; int update_delay = 0; debugr("ccwin_set_color called\n"); if (p->nowin) { debugr("ccwin_set_color: nowin - give up\n"); return 1; } orgb[0] = p->rgb[0]; p->rgb[0] = r; orgb[1] = p->rgb[1]; p->rgb[1] = g; orgb[2] = p->rgb[2]; p->rgb[2] = b; for (j = 0; j < 3; j++) { if (p->rgb[j] < 0.0) p->rgb[j] = 0.0; else if (p->rgb[j] > 1.0) p->rgb[j] = 1.0; p->r_rgb[j] = p->s_rgb[j] = p->rgb[j]; if (p->out_tvenc) { p->r_rgb[j] = p->s_rgb[j] = ((235.0 - 16.0) * p->s_rgb[j] + 16.0)/255.0; /* For video encoding the extra bits of precision are created by bit shifting */ /* rather than scaling, so we need to scale the fp value to account for this. */ if (p->pdepth > 8) p->r_rgb[j] = (p->s_rgb[j] * 255 * (1 << (p->pdepth - 8))) /((1 << p->pdepth) - 1.0); } } /* This is probably not actually thread safe... */ p->ncix++; #if DDITHER != 1 # pragma message("############################# ccwin.c DDITHER != 1 ##") #endif /* Turn the color into a png file */ { /* We want a raster of IWIDTH x IHEIGHT pixels for web server, */ /* or p->w x p->h for PNG direct. */ render2d *r; prim2d *rct; depth2d depth = bpc8_2d; #if DDITHER == 1 int dither = 0x8002; /* 0x8002 = error diffuse FG only */ #elif DDITHER == 2 int dither = 0x4000; /* 0x4000 = no dither but don't average pixels */ /* so as to allow pattern to come through. */ #else int dither = 0; /* Don't dither in renderer */ #endif double hres = 1.0; /* Resoltion in pix/mm */ double vres = 1.0; /* Resoltion in pix/mm */ double iw, ih; /* Size of page in mm (pixels) */ double bg[3]; /* Background color */ color2d c; unsigned char *ibuf; /* Memory image of .png file */ size_t ilen; #ifdef DO_TIMING int stime; #endif if (ws->direct) { iw = ws->w; /* Requested size */ ih = ws->h; } else { iw = IWIDTH; ih = IHEIGHT; /* Size of page in mm */ } if (p->blackbg) { bg[0] = 0.0; bg[1] = 0.0; bg[2] = 0.0; } else { bg[0] = 0.5; bg[1] = 0.5; bg[2] = 0.5; } debugr2((errout, "ccwin_set_color iw %f ih %f\n",iw,ih)); if ((r = new_render2d(iw, ih, NULL, hres, vres, rgb_2d, 0, depth, dither, #if DDITHER == 1 ccastQuant, #else NULL, #endif NULL)) == NULL) { error("ccwin: new_render2d() failed"); } /* Set the background color */ c[0] = bg[0]; c[1] = bg[1]; c[2] = bg[2]; r->set_defc(r, c); c[0] = p->r_rgb[0]; c[1] = p->r_rgb[1]; c[2] = p->r_rgb[2]; if (ws->direct) r->add(r, rct = new_rect2d(r, 0.0, 0.0, ws->w, ws->h, c)) ; else r->add(r, rct = new_rect2d(r, ws->x, ws->y, ws->w, ws->h, c)) ; #if DDITHER == 2 /* Use dither pattern */ { double rgb[3]; double dpat[CCDITHSIZE][CCDITHSIZE][3]; double (*cpat)[MXPATSIZE][MXPATSIZE][TOTC2D]; int i, j; /* Get a chrome cast dither pattern to match target color */ for (i = 0; i < 3; i++) rgb[i] = p->r_rgb[i] * 255.0; get_ccast_dith(dpat, rgb); if ((cpat = malloc(sizeof(double) * MXPATSIZE * MXPATSIZE * TOTC2D)) == NULL) error("ccwin: malloc of dither pattern failed"); for (i = 0; i < CCDITHSIZE; i++) { for (j = 0; j < CCDITHSIZE; j++) { int k = (((int)IHEIGHT-2) - j) % CCDITHSIZE; /* Flip to origin bot left */ (*cpat)[i][k][0] = dpat[i][j][0]/255.0; /* (HEIGHT-2 is correct!) */ (*cpat)[i][k][1] = dpat[i][j][1]/255.0; (*cpat)[i][k][2] = dpat[i][j][2]/255.0; } } set_rect2d_dpat((rect2d *)rct, cpat, CCDITHSIZE, CCDITHSIZE); } #endif /* DDITHER == 2 */ #ifdef CCTEST_PATTERN #pragma message("############################# ccwin.c TEST_PATTERN defined ! ##") if (getenv("ARGYLL_CCAST_TEST_PATTERN") != NULL) { verbose(0, "Writing test pattern to '%s'\n","testpattern.png"); if (r->write(r, "testpattern.png", 1, NULL, NULL, png_file)) error("ccwin: render->write failed"); } #else /* !CCTEST_PATTERN */ # ifdef WRITE_PNG /* Write it to a file so that we can look at it */ # pragma message("############################# spectro/ccwin.c WRITE_PNG is enabled ######") if (r->write(r, "ccwin.png", 1, NULL, NULL, png_file)) error("ccwin: render->write failed"); # endif /* WRITE_PNG */ #endif /* !CCTEST_PATTERN */ #ifdef DO_TIMING stime = msec_time(); #endif if (r->write(r, "MemoryBuf", 1, &ibuf, &ilen, png_mem)) error("ccwin: render->write failed"); #ifdef DO_TIMING stime = msec_time() - stime; printf("render->write took %d msec\n",stime); #endif if (ws->update(ws, ibuf, ilen, bg)) error("ccwin: color update failed"); p->ccix = p->ncix; } /* If update is notified asyncronously ... */ // while(p->ncix != p->ccix) { // msec_sleep(50); // } //printf("#################################################################\n"); //printf("################# RGB update notified ################\n"); //printf("#################################################################\n"); /* Allow for display update & instrument delays */ update_delay = dispwin_compute_delay(p, orgb); debugr2((errout, "ccwin_set_color delaying %d msec\n",update_delay)); msec_sleep(update_delay); return 0; } /* Set/unset the blackground color flag */ /* Return nz on error */ static int ccwin_set_bg(dispwin *p, int blackbg) { p->blackbg = blackbg; return 0; } /* ----------------------------------------------- */ /* Set the shell set color callout */ void ccwin_set_callout( dispwin *p, char *callout ) { debugr2((errout,"ccwin_set_callout called with '%s'\n",callout)); p->callout = strdup(callout); } /* ----------------------------------------------- */ /* Destroy ourselves */ static void ccwin_del( dispwin *p ) { chws *ws; debugr("ccwin_del called\n"); if (p == NULL) return; ws = (chws *)p->pcntx; if (ws != NULL) ws->del(ws); if (p->name != NULL) free(p->name); if (p->description != NULL) free(p->description); if (p->callout != NULL) free(p->callout); free(p); } /* ----------------------------------------------- */ /* Create a web display test window, default grey */ dispwin *new_ccwin( ccast_id *cc_id, /* ChromeCast to open */ double width, double height, /* Width and height in mm. (TV width assumed to b 1000mm) */ double hoff, double voff, /* Offset from center in fraction of screen, range -1.0 .. 1.0 */ int nowin, /* NZ if no window should be created - RAMDAC access only */ int native, /* X0 = use current per channel calibration curve */ /* X1 = set native linear output and use ramdac high precn. */ /* 0X = use current color management cLut (MadVR) */ /* 1X = disable color management cLUT (MadVR) */ int *noramdac, /* Return nz if no ramdac access. native is set to X0 */ int *nocm, /* Return nz if no CM cLUT access. native is set to 0X */ int out_tvenc, /* 1 = use RGB Video Level encoding */ int blackbg, /* NZ if whole screen should be filled with black */ int verb, /* NZ for verbose prompts */ int ddebug /* >0 to print debug statements to stderr */ ) { dispwin *p = NULL; char *cp; chws *ws = NULL; const char *options[3]; debug("new_ccwin called\n"); if ((p = (dispwin *)calloc(sizeof(dispwin), 1)) == NULL) { if (ddebug) fprintf(stderr,"new_ccwin failed because malloc failed\n"); return NULL; } /* !!!! Make changes in dispwin.c & madvrwin.c as well !!!! */ p->name = strdup("Web Window"); p->width = width; p->height = height; p->nowin = nowin; p->native = native; p->out_tvenc = out_tvenc; p->blackbg = blackbg; p->ddebug = ddebug; p->get_ramdac = ccwin_get_ramdac; p->set_ramdac = ccwin_set_ramdac; p->install_profile = ccwin_install_profile; p->uninstall_profile = ccwin_uninstall_profile; p->get_profile = ccwin_get_profile; p->set_color = ccwin_set_color; p->set_bg = ccwin_set_bg; p->set_update_delay = dispwin_set_update_delay; p->set_settling_delay = dispwin_set_settling_delay; p->enable_update_delay = dispwin_enable_update_delay; p->set_callout = ccwin_set_callout; p->del = ccwin_del; if (noramdac != NULL) *noramdac = 1; p->native &= ~1; if (nocm != NULL) *nocm = 1; p->native &= ~2; p->rgb[0] = p->rgb[1] = p->rgb[2] = 0.5; /* Set Grey as the initial test color */ dispwin_set_default_delays(p); p->ncix = 1; p->pdepth = 8; /* Assume this by API */ p->edepth = 8; /* Basic object is initialised, so create connection to ChromeCast */ if ((ws = new_chws(cc_id, width, height, hoff, voff, verb, ddebug)) == NULL) { if (ddebug) fprintf(stderr,"new_ccwin failed - new_chws() failed\n"); return NULL; } /* Extra delay ccast adds after confirming load */ p->extra_update_delay = ws->cc->get_load_delay(ws->cc) / 1000.0; p->pcntx = (void *)ws; /* Create a suitable description */ { char buf[100]; sprintf(buf,"ChromeCast '%s'",cc_id->name); p->description = strdup(buf); } // Set a default first color if (ccwin_set_color(p, 128.0, 128.0, 128.0)) { if (ddebug) fprintf(stderr,"new_ccwin set_color()\n"); p->del(p); return NULL; } debugr("new_ccwin: return sucessfully\n"); return p; }