private void ExecuteTask(object state) { CloudQueueMessage queueMessage = (CloudQueueMessage)state; this.Log.DebugFormat("Executing render task {0}", queueMessage.AsString); Data.Comic comic = null; RenderTask task = null; string connectionString = ConfigurationManager.ConnectionStrings["ComicModelContext"].ConnectionString; ComicModelContext entityContext = new ComicModelContext(connectionString); try { // Read task details from storage CloudBlobContainer container = this.BlobClient.GetContainerReference(ComicConfigSectionGroup.Blob.TaskContainer); CloudBlobDirectory directory = container.GetDirectoryReference(ComicConfigSectionGroup.Blob.RenderTaskDirectory); CloudBlob blob = directory.GetBlobReference(queueMessage.AsString); XmlSerializer serializer = new XmlSerializer(typeof(RenderTask)); using (MemoryStream stream = new MemoryStream()) { blob.DownloadToStream(stream); stream.Seek(0, SeekOrigin.Begin); task = (RenderTask)serializer.Deserialize(stream); task.Status = TaskStatus.Executing; this.UpdateTask(task); } User user = entityContext.TryGetUser(task.OwnerUid); FacebookClient facebook = new FacebookClient(task.FacebookToken); // Get template Template template = entityContext.ListTemplates().First(t => t.TemplateId == task.TemplateId); List <TemplateItem> templateItems = template.TemplateItems.OrderBy(t => t.Ordinal).ToList(); List <TextBubbleDirection> bubbles = entityContext.ListTextBubbles().SelectMany(b => b.TextBubbleDirections).ToList(); TextBubble speechBubble = entityContext.ListTextBubbles().First(b => b.Title == "speech"); TextBubble bubbleShout = entityContext.ListTextBubbles().First(b => b.Title == "shout"); TextBubble squareBubble = entityContext.ListTextBubbles().First(b => b.Title == "square"); comic = new Data.Comic(); entityContext.AddToComics(comic); comic.Author = user; comic.Template = template; comic.CreateTime = DateTime.Now; comic.UpdateTime = DateTime.Now; comic.PublishTime = null; comic.FeatureTime = null; comic.Title = ""; comic.Description = ""; comic.ShareText = ""; comic.IsPublished = false; comic.IsPrivate = false; comic.IsDeleted = false; comic.Locale = user.Locale ?? "en-US"; comic.StorageKey = Guid.NewGuid().ToString(); if (task.RemixComicId.HasValue) { comic.RemixedComic = entityContext.TryGetComic(task.RemixComicId.Value, user); } // Comic generator only used to size text ComicGenerator generator = new ComicGenerator(template.Width, template.Height); // Render effect parameters Dictionary <string, object> parameterValues = new Dictionary <string, object>(); switch (task.Effect) { case ComicEffectType.ColorSketch: case ComicEffectType.PencilSketch: parameterValues.Add("edging", 2); parameterValues.Add("coloring", 35); break; case ComicEffectType.Comic: default: parameterValues.Add("coloring", 6); break; } // Get photos for each frame for (int f = 0; f < task.Frames.Count && f < templateItems.Count; f++) { Photo photo = null; Bitmap image = null; string imageUrl = String.Empty; ComicGenerator.ImageAlign imageAlignment = ComicGenerator.ImageAlign.Center; Point tag = Point.Empty; bool tagConfident = false; if (task.PhotoSource == "Internal" && task.Frames[f].PhotoId.HasValue) { // Load image from database photo = entityContext.TryGetPhoto(task.Frames[f].PhotoId.Value); photo.ImageData = this.GetStoredImage(photo.StorageKey); image = new Bitmap(new MemoryStream(photo.ImageData)); } else { // Tagged facebook photos if (task.PhotoSource == "Tagged") { try { // List photos of the user Dictionary <string, object> args = new Dictionary <string, object>(); args.Add("limit", "50"); dynamic photoResult = facebook.Get(String.Format("/{0}/photos", task.Frames[f].Id), args); if (photoResult.data.Count > 0) { // Pick a random photo with 2 or fewer tags dynamic photoData = ((IList <dynamic>)photoResult.data) .OrderBy(p => Guid.NewGuid()) .FirstOrDefault(p => p.tags.data.Count <= 2); if (photoData != null) { imageUrl = (string)photoData.source; // Look for user tag location int id; dynamic tagData = ((IList <dynamic>)photoData.tags.data) .FirstOrDefault(t => int.TryParse(t.id, out id) && id == task.Frames[f].Id); if (tagData != null) { tag = new Point((int)Math.Round((double)tagData.x), (int)Math.Round((double)tagData.y)); tagConfident = false; } } } } catch (Exception x) { this.Log.Error("Unable to retrieve tagged photo from facebook.", x); } } // Look for any photo of the user else if (task.PhotoSource == "Any") { try { FaceRestAPI faceApi = this.CreateFaceApi(task.FacebookToken, user.Uid); List <string> ids = new List <string>(new string[] { String.Format("{0}@facebook.com", task.Frames[f].Id) }); FaceRestAPI.FaceAPI anyResult = faceApi.facebook_get(ids, null, "1", null, "random"); if (anyResult.status == "success" && anyResult.photos.Count > 0) { FaceRestAPI.Photo p = anyResult.photos[0]; imageUrl = p.url; tag = new Point((int)Math.Round(p.tags.First().mouth_center.x), (int)Math.Round(p.tags.First().mouth_center.y)); tagConfident = true; } } catch (Exception x) { this.Log.Error("Unable to retrieve photo through face.com api.", x); } } // Use profile photo as backup image if (String.IsNullOrEmpty(imageUrl)) { imageUrl = String.Format("https://graph.facebook.com/{0}/picture?access_token={1}&type=large", task.Frames[f].Id, facebook.AccessToken); } image = this.GetImage(imageUrl); // Find faces when confidence in tag location is low if (!tagConfident) { try { FaceRestAPI tagApi = this.CreateFaceApi(task.FacebookToken, user.Uid); //List<string> tagIds = new List<string>(new string[] { String.Format("{0}@facebook.com", task.Frames[f].Id) }); List <string> urls = new List <string>(new string[] { imageUrl }); FaceRestAPI.FaceAPI tagResult = tagApi.faces_detect(urls, null, "Normal", null, null); if (tagResult.status == "success" && tagResult.photos.Count > 0 && tagResult.photos[0].tags.Count > 0) { FaceRestAPI.Tag t = tagResult.photos[0].tags.First(); tag = new Point((int)Math.Round(t.mouth_center.x), (int)Math.Round(t.mouth_center.y)); tagConfident = true; } } catch (Exception x) { this.Log.Error("Unable to detected faces.", x); } } if (tag != Point.Empty && tag.Y <= image.Height / 3) { imageAlignment = ComicGenerator.ImageAlign.Top; } } // Resize to fit frame image = ComicGenerator.FitImage(new Size(templateItems[f].Width, templateItems[f].Height), image); // Apply render effect if (task.Effect != ComicEffectType.None) { RenderHelper effectHelper = new RenderHelper(image.Size); ImageRenderData renderResult = effectHelper.RenderEffect(image, task.Effect, parameterValues); image = new Bitmap(renderResult.RenderStream); } // Read raw photo into memory MemoryStream imageStream = new MemoryStream(); image.Save(imageStream, System.Drawing.Imaging.ImageFormat.Jpeg); imageStream.Seek(0, SeekOrigin.Begin); // Frame text bubbles if (!String.IsNullOrWhiteSpace(task.Frames[f].Message)) { ComicTextBubble comicBubble = new ComicTextBubble(); entityContext.AddToComicTextBubbles(comicBubble); comicBubble.Comic = comic; comicBubble.Text = task.Frames[f].Message; // Remove newlines comicBubble.Text = comicBubble.Text.Replace('\n', ' '); // Font size int fontSize = 7; if (comicBubble.Text.Length > 160) { fontSize = 6; } if (comicBubble.Text.Length > 200) { fontSize = 5; } comicBubble.Font = new Font(ComicGenerator.ComicFont, fontSize, FontStyle.Regular, GraphicsUnit.Point); // Shouting / excited? TextBubble bubble = speechBubble; if (comicBubble.Text.Contains('!') || Regex.Matches(comicBubble.Text, "[A-Z]").Count > comicBubble.Text.Length / 4) { bubble = bubbleShout; } // Calculate tag x/y coords relative to the whole comic if (tag != Point.Empty) { Size templateSize = new Size(templateItems[f].Width, templateItems[f].Height); Rectangle cropArea = ComicGenerator.GetCropImageSize(image.Size, templateSize, imageAlignment); tag.X = image.Size.Width * tag.X / 100 - cropArea.X + templateItems[f].X; tag.Y = image.Size.Height * tag.Y / 100 - cropArea.Y + templateItems[f].Y; } // Position text bubble this.PositionFrameBubble(comicBubble, image, generator, bubble, squareBubble, templateItems[f], tag, imageAlignment); // Add photo as template item photo = new Photo(); photo.User = user; photo.CreateTime = DateTime.Now; photo.ImageData = imageStream.ToArray(); photo.StorageKey = Guid.NewGuid().ToString(); photo.Width = image.Width; photo.Height = image.Height; entityContext.AddToPhotos(photo); } // Tag users //if (task.Frames[f].Id > 0) //{ // try // { // // Lookup existing user // User taggedUser = entityContext.TryGetUser(task.Frames[f].Id, true); // if (taggedUser == null) // { // // User doesn't exist in the db yet - grab from facebook // dynamic facebookUser = facebook.Get(String.Format("/{0}", task.Frames[f].Id)); // taggedUser = new User(); // taggedUser.Uid = long.Parse(facebookUser.id); // taggedUser.IsDeleted = false; // taggedUser.IsSubscribed = false; // taggedUser.Locale = facebookUser.locale; // taggedUser.Name = facebookUser.name; // taggedUser.Nickname = facebookUser.name; // taggedUser.FbLink = facebookUser.link; // entityContext.AddToUsers(taggedUser); // } // ComicTag comicTag = new ComicTag(); // comicTag.User = taggedUser; // comicTag.Comic = comic; // if (tag != Point.Empty) // { // comicTag.X = tag.X; // comicTag.Y = tag.Y; // } // } // catch (Exception x) // { // this.Log.ErrorFormat("Failed to tag user {0} in comic. {1}", task.Frames[f].Id, x.ToString()); // } //} ComicPhoto comicPhoto = new ComicPhoto(); comicPhoto.Comic = comic; comicPhoto.Photo = photo; comicPhoto.TemplateItem = templateItems[f]; comicPhoto.Alignment = imageAlignment; comic.ComicPhotos.Add(comicPhoto); // Update task progress task.CompletedOperations++; this.UpdateTask(task); } for (int b = 0; task.Bubbles != null && b < task.Bubbles.Count; b++) { ComicTextBubble comicBubble = new ComicTextBubble(); entityContext.AddToComicTextBubbles(comicBubble); comicBubble.Comic = comic; comicBubble.Text = task.Bubbles[b].Text; comicBubble.Font = new Font(ComicGenerator.ComicFont, 7, FontStyle.Regular, GraphicsUnit.Point); comicBubble.TextBubbleDirection = bubbles.First(d => d.TextBubbleDirectionId == task.Bubbles[b].TextBubbleDirectionId); comicBubble.Position = new Rectangle(new Point(task.Bubbles[b].X, task.Bubbles[b].Y), generator.MeasureText(comicBubble.Text, comicBubble.Font).ToSize()); } // Fix for position to x,y coordinates foreach (ComicTextBubble b in comic.ComicTextBubbles) { b.X = b.Position.X; b.Y = b.Position.Y; } this.SaveComic(comic, entityContext); task.CompletedOperations = task.TotalOperations; task.Status = TaskStatus.Complete; task.ComicId = comic.ComicId; this.UpdateTask(task); this.Log.DebugFormat("Completed render task {0}", task.TaskId); } catch (Exception x) { this.Log.Error("Unable to complete render task.", x); if (task != null) { task.Status = TaskStatus.Failed; this.UpdateTask(task); } if (comic != null) { this.Log.DebugFormat("Text bubble info [{0}] [{1}]", String.Join(",", comic.ComicTextBubbles.Select(b => b.ComicTextBubbleId.ToString()).ToArray()), String.Join(",", comic.ComicTextBubbles.Select(b => b.TextBubbleDirectionId.ToString()).ToArray())); } } }
private void PositionFrameBubble(ComicTextBubble comicBubble, Bitmap image, ComicGenerator generator, TextBubble bubble, TextBubble squareBubble, TemplateItem templateItem, Point tag, ComicGenerator.ImageAlign alignment) { Rectangle textArea = new Rectangle(); // Measure text Size rawTextSize = generator.MeasureText(comicBubble.Text, comicBubble.Font).ToSize(); int charWidth = rawTextSize.Width / comicBubble.Text.Length; // Calc text bubble dimensions based on bubble x:y ratio Size textSize = this.CalculateTextSize(comicBubble.Text, rawTextSize, bubble); textSize.Width = Math.Min(textSize.Width, templateItem.Width); // Don't exceede template item area // Final measure textSize = generator.MeasureText(comicBubble.Text, textSize.Width, comicBubble.Font).ToSize(); textSize.Width += 6; // Measure error ? // Photos are anchored at the top. Sides and bottom are cropped to fit //Size templateSize = new Size(templateItem.Width, templateItem.Height); Rectangle templateArea = new Rectangle(templateItem.X, templateItem.Y, templateItem.Width, templateItem.Height); int centerOffset = (templateItem.Width - textSize.Width) / 2; if (tag != Point.Empty) { //Size fitSize = ComicGenerator.GetFitImageSize(new Size(image.Width, image.Height), templateSize); //Rectangle cropArea = ComicGenerator.GetCropImageSize(fitSize, templateSize, alignment); // Get fitSize face location //int tagX = fitSize.Width * tag.X / 100 - cropArea.X + templateItem.X; //int tagY = fitSize.Height * tag.Y / 100 - cropArea.Y + templateItem.Y; int bufferX = textSize.Width / 4 + 10; int bufferY = textSize.Height / 2 + 30; // Attempt to position around the tag Rectangle t = new Rectangle(tag.X - (textSize.Width / 2), tag.Y - textSize.Height - bufferY, textSize.Width, textSize.Height); Rectangle tl = new Rectangle(tag.X - textSize.Width - bufferX, tag.Y - textSize.Height - bufferY, textSize.Width, textSize.Height); Rectangle tr = new Rectangle(tag.X + bufferX, tag.Y - textSize.Height - bufferY, textSize.Width, textSize.Height); Rectangle bl = new Rectangle(tag.X - textSize.Width - bufferX, tag.Y + bufferY, textSize.Width, textSize.Height); Rectangle br = new Rectangle(tag.X + bufferX, tag.Y + bufferY, textSize.Width, textSize.Height); Rectangle b = new Rectangle(tag.X - (textSize.Width / 2), tag.Y + bufferY, textSize.Width, textSize.Height); // Allow for text to appear slightly outside the frame, but not outside the bounds of the comic Rectangle bubbleArea = new Rectangle(Math.Max(0, templateArea.X - 25), Math.Max(0, templateArea.Y - 25), templateArea.Width + 25, templateArea.Height); if (bubbleArea.X + bubbleArea.Width > templateItem.Template.Width) { bubbleArea.Width -= bubbleArea.X + bubbleArea.Width - templateItem.Template.Width; } if (bubbleArea.Y + bubbleArea.Height > templateItem.Template.Height) { bubbleArea.Height -= bubbleArea.Y + bubbleArea.Height - templateItem.Template.Height; } if (bubbleArea.Contains(br) && !comicBubble.Comic.ComicTextBubbles.Any(c => c.Position.IntersectsWith(br))) { textArea = br; comicBubble.TextBubbleDirection = bubble.TextBubbleDirections.First(d => d.Direction == "tl"); comicBubble.Position = br; } if (bubbleArea.Contains(tr) && !comicBubble.Comic.ComicTextBubbles.Any(c => c.Position.IntersectsWith(tr))) { textArea = tr; comicBubble.TextBubbleDirection = bubble.TextBubbleDirections.First(d => d.Direction == "bl"); comicBubble.Position = tr; } else if (bubbleArea.Contains(bl) && !comicBubble.Comic.ComicTextBubbles.Any(c => c.Position.IntersectsWith(bl))) { textArea = bl; comicBubble.TextBubbleDirection = bubble.TextBubbleDirections.First(d => d.Direction == "tr"); comicBubble.Position = bl; } else if (bubbleArea.Contains(tl) && !comicBubble.Comic.ComicTextBubbles.Any(c => c.Position.IntersectsWith(tl))) { textArea = tl; comicBubble.TextBubbleDirection = bubble.TextBubbleDirections.First(d => d.Direction == "br"); comicBubble.Position = tl; } else if (bubbleArea.Contains(b) && !comicBubble.Comic.ComicTextBubbles.Any(c => c.Position.IntersectsWith(b))) { textArea = b; comicBubble.TextBubbleDirection = bubble.TextBubbleDirections.First(d => d.Direction == "t"); comicBubble.Position = b; } else if (bubbleArea.Contains(t) && !comicBubble.Comic.ComicTextBubbles.Any(c => c.Position.IntersectsWith(t))) { comicBubble.TextBubbleDirection = bubble.TextBubbleDirections.First(d => d.Direction == "b"); comicBubble.Position = t; } else { if (tag.Y <= 50) { // Position at bottom - face is at top comicBubble.TextBubbleDirection = squareBubble.TextBubbleDirections.First(d => d.Direction == "n"); comicBubble.Position = new Rectangle(templateItem.X + centerOffset, templateItem.Y + templateItem.Height - textSize.Height, textSize.Width, textSize.Height); } else { // Position at top - face is at the bottom comicBubble.TextBubbleDirection = squareBubble.TextBubbleDirections.First(d => d.Direction == "n"); comicBubble.Position = new Rectangle(templateItem.X + centerOffset, templateItem.Y, textSize.Width, textSize.Height); } } } else { comicBubble.TextBubbleDirection = squareBubble.TextBubbleDirections.First(d => d.Direction == "n"); comicBubble.Position = new Rectangle(templateItem.X + centerOffset, templateItem.Y + templateItem.Height - textSize.Height, textSize.Width, textSize.Height); } }
public ViewResult Create(long?id) { ClientComic comic = null; List <ClientTemplate> templates = this.EntityContext.ListTemplates() .ToList() .Select(t => new ClientTemplate(t)) .ToList(); foreach (ClientTemplate t in templates) { int width = Math.Min(t.Width, 734); double factor = Convert.ToDouble(width) / Convert.ToDouble(t.Width); t.Width = width; t.Height = Convert.ToInt32(Convert.ToDouble(t.Height) * factor); foreach (ClientTemplateItem i in t.TemplateItems) { i.Width = Convert.ToInt32(Convert.ToDouble(i.Width) * factor); i.Height = Convert.ToInt32(Convert.ToDouble(i.Height) * factor); i.X = Convert.ToInt32(Convert.ToDouble(i.X) * factor); i.Y = Convert.ToInt32(Convert.ToDouble(i.Y) * factor); } } if (id.HasValue) { // Facebook shared comics Data.Comic c = this.EntityContext.TryGetComic(id.Value, this.ActiveUser) ?? this.EntityContext.TryGetUnpublishedComic(id.Value, this.ActiveUser); if (c != null) { c.ComicTextBubbles.Load(); c.ComicPhotos.Load(); comic = new ClientComic(c); // inject newlines into text int maxWidth = templates.SelectMany(t => t.TemplateItems).Select(t => t.Width).Min(); Font measureFont = new Font(ComicGenerator.ComicFont, 7, FontStyle.Regular, GraphicsUnit.Point); ComicGenerator generator = new ComicGenerator(templates.Min(t => t.Width), templates.Min(t => t.Height)); foreach (ComicTextBubble bubble in c.ComicTextBubbles) { Size rawTextSize = generator.MeasureText(bubble.Text, maxWidth, measureFont).ToSize(); Size textSize = this.CalculateTextSize(bubble.Text, rawTextSize, bubble.TextBubbleDirection.TextBubble); textSize.Width = Math.Min(textSize.Width, maxWidth); // Don't exceede template item area textSize = generator.MeasureText(bubble.Text, textSize.Width, measureFont).ToSize(); textSize.Width += 6; // Measure error ? int lines = textSize.Height / measureFont.Height; int cutWidth = (bubble.Text.Length / lines) - 4; for (int l = 1; l < lines; l++) { int cut = cutWidth * l; int space = bubble.Text.IndexOf(' ', cut); if (space > 0) { bubble.Text = bubble.Text.ReplaceAt(space, '\n'); } } comic.Bubbles.First(b => b.ComicTextBubbleId == bubble.ComicTextBubbleId).Text = bubble.Text; } } } List <ClientTextBubbleDirection> bubbles = this.EntityContext.ListTextBubbles() .SelectMany(b => b.TextBubbleDirections) .ToList() .Select(d => new ClientTextBubbleDirection(d)) .ToList(); return(this.View(new ViewCreate(comic, templates, this.GetEffects(), bubbles))); }