public void ProcessRequest(HttpContext context) { // -------------------------------------------------------------------------------------------- // 1) PARSE URL PARAMETERS // -------------------------------------------------------------------------------------------- // See Global.asax: // render/{width}/{height}/{scale}/{file}/{anim}/{palette}/{angle} RouteValueDictionary parms = context.Request.RequestContext.RouteData.Values; string parmWidth = parms.ContainsKey("width") ? (string)parms["width"] : null; string parmHeight = parms.ContainsKey("height") ? (string)parms["height"] : null; string parmScale = parms.ContainsKey("scale") ? (string)parms["scale"] : null; string parmFile = parms.ContainsKey("file") ? (string)parms["file"] : null; string parmAnim = parms.ContainsKey("anim") ? (string)parms["anim"] : null; string parmPalette = parms.ContainsKey("palette") ? (string)parms["palette"] : null; string parmAngle = parms.ContainsKey("angle") ? (string)parms["angle"] : null; BgfCache.Entry entry; byte paletteidx; ushort angle; ushort width; ushort height; ushort scale; // verify that minimum parameters are valid/in range and bgf exists // angle ranges from [0-7] and are multiples of 512 if (!UInt16.TryParse(parmWidth, out width) || width < MINWIDTH || width > MAXWIDTH || !UInt16.TryParse(parmHeight, out height) || height < MINHEIGHT || height > MAXHEIGHT || !UInt16.TryParse(parmScale, out scale) || scale < MINSCALE || scale > MAXSCALE || !Byte.TryParse(parmPalette, out paletteidx) || !UInt16.TryParse(parmAngle, out angle) || angle > 7 || String.IsNullOrEmpty(parmFile) || !BgfCache.GetBGF(parmFile, out entry)) { Finish(context, 404); return; } // multiply by 512 and remove full periods from angle angle = (ushort)((angle << 9) % GeometryConstants.MAXANGLE); // parse animation Animation anim = Animation.ExtractAnimation(parmAnim, '-'); if (anim == null || !anim.IsValid(entry.Bgf.FrameSets.Count)) { Finish(context, 404); return; } // set groupmax anim.GroupMax = entry.Bgf.FrameSets.Count; // stores the latest lastmodified of main and all subov DateTime lastModified = entry.LastModified; // set parsed/created values on game-object ObjectBase gameObject = new ObjectBase(); gameObject.OverlayFileRID = entry.Num; gameObject.Resource = entry.Bgf; gameObject.ColorTranslation = paletteidx; gameObject.Animation = anim; gameObject.ViewerAngle = angle; // read suboverlay array params from query parameters: // object/..../?subov={file};{anim};{palette};{hotspot}&subov=... string[] parmSubOverlays = context.Request.Params.GetValues("s"); if (parmSubOverlays != null) { foreach (string s in parmSubOverlays) { string[] subOvParms = s.Split(';'); BgfCache.Entry bgfSubOv; byte subOvPalette; byte subOvHotspot; if (subOvParms == null || subOvParms.Length < 4 || String.IsNullOrEmpty(subOvParms[0]) || String.IsNullOrEmpty(subOvParms[1]) || !byte.TryParse(subOvParms[2], out subOvPalette) || !byte.TryParse(subOvParms[3], out subOvHotspot) || subOvHotspot > 127 || !BgfCache.GetBGF(subOvParms[0], out bgfSubOv)) { Finish(context, 404); return; } // get suboverlay animation Animation subOvAnim = Animation.ExtractAnimation(subOvParms[1], '-'); if (subOvAnim == null || !subOvAnim.IsValid(bgfSubOv.Bgf.FrameSets.Count)) { Finish(context, 404); return; } // set group max subOvAnim.GroupMax = bgfSubOv.Bgf.FrameSets.Count; // create suboverlay SubOverlay subOv = new SubOverlay(0, subOvAnim, subOvHotspot, subOvPalette, 0); // set bgf resource subOv.ResourceID = entry.Num; subOv.Resource = bgfSubOv.Bgf; // update lastModified if subov is newer if (bgfSubOv.LastModified > lastModified) { lastModified = bgfSubOv.LastModified; } // add to gameobject's suboverlays gameObject.SubOverlays.Add(subOv); } } // -------------------------------------------------------------------------------------------- // 2) SET CACHE HEADERS AND COMPARE FOR 304 RETURN // -------------------------------------------------------------------------------------------- // set cache behaviour context.Response.Cache.SetCacheability(HttpCacheability.Public); context.Response.Cache.VaryByParams["*"] = false; context.Response.Cache.SetLastModified(lastModified); // set return type context.Response.ContentType = "image/gif"; context.Response.AddHeader("Content-Disposition", "inline; filename=object.gif"); // check if client has valid cached version (returns 304) /*DateTime dateIfModifiedSince; * string modSince = context.Request.Headers["If-Modified-Since"]; * * // try to parse received client header * bool parseOk = DateTime.TryParse( * modSince, CultureInfo.CurrentCulture, * DateTimeStyles.AdjustToUniversal, out dateIfModifiedSince); * * // send 304 and stop if last file write equals client's cache timestamp * if (parseOk && dateIfModifiedSince == lastModified) * { * context.Response.SuppressContent = true; * Finish(context, 304); * return; * }*/ // -------------------------------------------------------------------------------------------- // 3) PREPARE RESPONSE // -------------------------------------------------------------------------------------------- // set gif instance size gif.CanvasWidth = width; gif.CanvasHeight = height; // set imagecomposer size and shrink imageComposer.Width = width; imageComposer.Height = height; imageComposer.CustomShrink = (float)scale * 0.1f; // set gameobject (causes initial frame) imageComposer.DataSource = gameObject; // run animationlength in 1 ms steps (causes new image events) for (int i = 0; i < gameObject.AnimationLength + 10; i++) { tick += 1.0; gameObject.Tick(tick, 1.0); } // handle single frame case if (gif.Frames.Count == 0 && frame != null) { gif.Frames.Add(frame); } // -------------------------------------------------------------------------------------------- // 4) CREATE RESPONSE AND FINISH // -------------------------------------------------------------------------------------------- // write the gif to output stream gif.Write(context.Response.OutputStream); // cleanup Finish(context); }