/// <summary> /// Currently used by the image server /// to get thumbnails that are used in the add page dialog. Since this dialog can show /// an enlarged version of the page, we generate these at a higher resolution than usual. /// Also, to make more realistic views of template pages we insert fake text wherever /// there is an empty edit block. /// /// The result is cached for possible future use so the caller should not dispose of it. /// </summary> /// <param name="book"></param> /// <param name="page"></param> /// <param name="isLandscape"></param> /// <param name="mustRegenerate"></param> /// <returns></returns> public Image GetThumbnailForPage(Book.Book book, IPage page, bool isLandscape, bool mustRegenerate = false) { var pageDom = book.GetThumbnailXmlDocumentForPage(page); var thumbnailOptions = new HtmlThumbNailer.ThumbnailOptions() { BackgroundColor = Color.White, // matches the hand-made previews. BorderStyle = HtmlThumbNailer.ThumbnailOptions.BorderStyles.None, // allows the HTML to add its preferred border in the larger preview CenterImageUsingTransparentPadding = true, MustRegenerate = mustRegenerate }; var pageDiv = pageDom.RawDom.SafeSelectNodes("descendant-or-self::div[contains(@class,'bloom-page')]").Cast <XmlElement>().FirstOrDefault(); // The actual page size is rather arbitrary, but we want the right ratio for A4. // Using the actual A4 sizes in mm makes a big enough image to look good in the larger // preview box on the right as well as giving exactly the ratio we want. // We need to make the image the right shape to avoid some sort of shadow/box effects // that I can't otherwise find a way to get rid of. if (isLandscape) { thumbnailOptions.Width = 297; thumbnailOptions.Height = 210; pageDiv.SetAttribute("class", pageDiv.Attributes["class"].Value.Replace("Portrait", "Landscape")); } else { thumbnailOptions.Width = 210; thumbnailOptions.Height = 297; // On the offchance someone makes a template with by-default-landscape pages... pageDiv.SetAttribute("class", pageDiv.Attributes["class"].Value.Replace("Landscape", "Portrait")); } // In different books (or even the same one) in the same session we may have portrait and landscape // versions of the same template page. So we must use different IDs. return(_thumbnailProvider.GetThumbnail(page.Id + (isLandscape ? "L" : ""), pageDom, thumbnailOptions)); }
/// <summary> /// Will call either 'callback' or 'errorCallback' UNLESS the thumbnail is readonly, in which case it will do neither. /// </summary> /// <param name="book"></param> /// <param name="thumbnailOptions"></param> /// <param name="callback"></param> /// <param name="errorCallback"></param> private void RebuildThumbNail(Book.Book book, HtmlThumbNailer.ThumbnailOptions thumbnailOptions, Action <BookInfo, Image> callback, Action <BookInfo, Exception> errorCallback, bool async) { try { if (!book.Storage.RemoveBookThumbnail(thumbnailOptions.FileName)) { // thumbnail is marked readonly, so just use it Image thumb; book.Storage.TryGetPremadeThumbnail(thumbnailOptions.FileName, out thumb); callback(book.BookInfo, thumb); return; } _thumbnailProvider.RemoveFromCache(book.Storage.Key); thumbnailOptions.BorderStyle = GetThumbnailBorderStyle(book); GetThumbNailOfBookCover(book, thumbnailOptions, image => callback(book.BookInfo, image), error => { //Enhance; this isn't a very satisfying time to find out, because it's only going to happen if we happen to be rebuilding the thumbnail. //It does help in the case where things are bad, so no thumbnail was created, but by then probably the user has already had some big error. //On the other hand, given that they have this bad book in their collection now, it's good to just remind them that it's broken and not //keep showing green error boxes. book.CheckForErrors(); errorCallback(book.BookInfo, error); }, async); } catch (Exception error) { NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, "Problem creating book thumbnail ", exception: error); } }
/// <summary> /// Check if the cover image is valid /// </summary> /// <returns>True if it's valid. It may be invalid if imageSrc is missing. In certain scenarios, if the imageSrc is "placeHolder.png", that's not valid either.</returns> internal static bool IsCoverImageSrcValid(string imageSrc, HtmlThumbNailer.ThumbnailOptions options) { if (string.IsNullOrEmpty(imageSrc)) { return(false); } else if (Path.GetFileName(imageSrc) == "placeHolder.png") { // Valid examples: // thumbnail.png // thumbnail-256.png // thumbnail-300x300.png if (Regex.IsMatch(options.FileName, "thumbnail(-[0-9]+(x[0-9]+)?)?\\.png")) { return(true); } else { return(false); } } else { return(true); } }
/// <summary> /// Currently used by the image server /// to get thumbnails that are used in the add page dialog. Since this dialog can show /// an enlarged version of the page, we generate these at a higher resolution than usual. /// Also, to make more realistic views of template pages we insert fake text wherever /// there is an empty edit block. /// /// The result is cached for possible future use so the caller should not dispose of it. /// </summary> /// <param name="book"></param> /// <param name="page"></param> /// <param name="isLandscape"></param> /// <returns></returns> public Image GetThumbnailForPage(Book.Book book, IPage page, bool isLandscape) { var pageDom = book.GetThumbnailXmlDocumentForPage(page); var thumbnailOptions = new HtmlThumbNailer.ThumbnailOptions() { BackgroundColor = Color.White,// matches the hand-made previews. BorderStyle = HtmlThumbNailer.ThumbnailOptions.BorderStyles.None, // allows the HTML to add its preferred border in the larger preview CenterImageUsingTransparentPadding = true }; var pageDiv = pageDom.RawDom.SafeSelectNodes("descendant-or-self::div[contains(@class,'bloom-page')]").Cast<XmlElement>().FirstOrDefault(); // The actual page size is rather arbitrary, but we want the right ratio for A4. // Using the actual A4 sizes in mm makes a big enough image to look good in the larger // preview box on the right as well as giving exactly the ratio we want. // We need to make the image the right shape to avoid some sort of shadow/box effects // that I can't otherwise find a way to get rid of. if (isLandscape) { thumbnailOptions.Width = 297; thumbnailOptions.Height = 210; pageDiv.SetAttribute("class", pageDiv.Attributes["class"].Value.Replace("Portrait", "Landscape")); } else { thumbnailOptions.Width = 210; thumbnailOptions.Height = 297; // On the offchance someone makes a template with by-default-landscape pages... pageDiv.SetAttribute("class", pageDiv.Attributes["class"].Value.Replace("Landscape", "Portrait")); } // In different books (or even the same one) in the same session we may have portrait and landscape // versions of the same template page. So we must use different IDs. return _thumbnailProvider.GetThumbnail(page.Id + (isLandscape ? "L" : ""), pageDom, thumbnailOptions); }
/// <summary> /// Will make a new thumbnail (or throw) UNLESS the thumbnail is readonly, in which case it will do nothing. /// </summary> /// <param name="book"></param> /// <param name="thumbnailOptions"></param> private void RebuildThumbNailNow(Book.Book book, HtmlThumbNailer.ThumbnailOptions thumbnailOptions) { RebuildThumbNail(book, thumbnailOptions, (info, image) => { }, (info, ex) => { throw ex; }, false); }
/// <summary> /// Creates a thumbnail of just the cover image (no title, language name, etc.) /// </summary> /// <returns>Returns true if successful; false otherwise. </returns> internal static bool CreateThumbnailOfCoverImage(Book.Book book, HtmlThumbNailer.ThumbnailOptions options, Action <Image> callback = null) { var imageSrc = book.GetCoverImagePath(); if (!IsCoverImageSrcValid(imageSrc, options)) { Debug.WriteLine(book.StoragePageFolder + " does not have a cover image."); return(false); } var size = Math.Max(options.Width, options.Height); var destFilePath = Path.Combine(book.StoragePageFolder, options.FileName); // Writing a transparent image to a file, then reading it in again appears to be the only // way to get the thumbnail image to draw with the book's cover color background reliably. var transparentImageFile = Path.Combine(Path.GetTempPath(), "Bloom", "Transparent", Path.GetFileName(imageSrc)); Directory.CreateDirectory(Path.GetDirectoryName(transparentImageFile)); try { if (RuntimeImageProcessor.MakePngBackgroundTransparent(imageSrc, transparentImageFile)) { imageSrc = transparentImageFile; } using (var coverImage = PalasoImage.FromFile(imageSrc)) { if (imageSrc == transparentImageFile) { coverImage.Image = MakeImageOpaque(coverImage.Image, book.GetCoverColor()); } var shouldAddDashedBorder = options.BorderStyle == HtmlThumbNailer.ThumbnailOptions.BorderStyles.Dashed; coverImage.Image = options.CenterImageUsingTransparentPadding ? ImageUtils.CenterImageIfNecessary(new Size(size, size), coverImage.Image, shouldAddDashedBorder) : ImageUtils.ResizeImageIfNecessary(new Size(size, size), coverImage.Image, shouldAddDashedBorder); switch (Path.GetExtension(destFilePath).ToLowerInvariant()) { case ".jpg": case ".jpeg": ImageUtils.SaveAsTopQualityJpeg(coverImage.Image, destFilePath); break; default: PalasoImage.SaveImageRobustly(coverImage, destFilePath); break; } if (callback != null) { callback(coverImage.Image.Clone() as Image); // don't leave GC to chance } } } finally { if (File.Exists(transparentImageFile)) { SIL.IO.RobustFile.Delete(transparentImageFile); } } return(true); }
/// <summary> /// Generates the cover image for a book, e.g. the thumbnail used on Bloom Library /// </summary> /// <param name="book"></param> /// <param name="requestedSize">The maximum size of either dimension</param> public static string GenerateCoverImageOfRequestedMaxSize(Book.Book book, int requestedSize) { HtmlThumbNailer.ThumbnailOptions thumbnailOptions = BookThumbNailer.GetCoverThumbnailOptions(requestedSize, new Guid()); BookThumbNailer.CreateThumbnailOfCoverImage(book, thumbnailOptions, null); string thumbnailDestination = Path.Combine(book.FolderPath, thumbnailOptions.FileName); return(thumbnailDestination); }
/// <summary> /// Generates a web thumbnail image from the book's front cover image. /// The resulting image will fit into a 200px x 200px box with no loss of aspect ratio. /// This means that either the height or the width will be 200px. /// </summary> /// <param name="book"></param> public static void GenerateImageForWeb(Book.Book book) { HtmlThumbNailer.ThumbnailOptions options = new HtmlThumbNailer.ThumbnailOptions { Height = 200, Width = 200, CenterImageUsingTransparentPadding = false, FileName = "coverImage200.jpg" }; CreateThumbnailOfCoverImage(book, options); }
private void GetThumbNailOfBookCover(Book.Book book, HtmlThumbNailer.ThumbnailOptions thumbnailOptions, Action <Image> callback, Action <Exception> errorCallback, bool async) { if (book is ErrorBook) { callback(Resources.Error70x70); return; } try { if (book.HasFatalError) //NB: we might not know yet... we don't fully load every book just to show its thumbnail { callback(Resources.Error70x70); return; } GenerateImageForWeb(book); Image thumb; if (book.Storage.TryGetPremadeThumbnail(thumbnailOptions.FileName, out thumb)) { callback(thumb); return; } #if USE_HTMLTHUMBNAILER_FOR_COVER var dom = book.GetPreviewXmlDocumentForFirstPage(); if (dom == null) { callback(Resources.Error70x70); return; } string folderForCachingThumbnail; folderForCachingThumbnail = book.StoragePageFolder; _thumbnailProvider.GetThumbnail(folderForCachingThumbnail, book.Storage.Key, dom, thumbnailOptions, callback, errorCallback, async); #else if (!CreateThumbnailOfCoverImage(book, thumbnailOptions, callback)) { callback(Resources.Error70x70); } #endif } catch (Exception err) { callback(Resources.Error70x70); errorCallback(err); Debug.Fail(err.Message); } }
/// <summary> /// Make a thumbnail image of a book's front cover. /// </summary> /// <param name="book"></param> /// <param name="height">Optional parameter. If unspecified, use defaults</param> public void MakeThumbnailOfCover(Book.Book book, int height = -1) { HtmlThumbNailer.ThumbnailOptions options = new HtmlThumbNailer.ThumbnailOptions { //since this is destined for HTML, it's much easier to handle if there is no pre-padding CenterImageUsingTransparentPadding = false }; if (height != -1) { options.Height = height; options.Width = -1; options.FileName = "thumbnail-" + height + ".png"; } // else use the defaults RebuildThumbNailNow(book, options); }
/// <summary> /// Gets the default thumbnail options to use when creating a thumbnail of a book's cover /// </summary> public static HtmlThumbNailer.ThumbnailOptions GetCoverThumbnailOptions(int height, Guid requestId) { var options = new HtmlThumbNailer.ThumbnailOptions { //since this is destined for HTML, it's much easier to handle if there is no pre-padding CenterImageUsingTransparentPadding = false, RequestId = requestId }; if (height != -1) { options.Height = height; options.Width = -1; options.FileName = "thumbnail-" + height + ".png"; } // else use the defaults return(options); }
public void GetThumbNailOfBookCover(Book.Book book, HtmlThumbNailer.ThumbnailOptions thumbnailOptions, Action <Image> callback, Action <Exception> errorCallback, bool async) { if (book is ErrorBook) { callback(Resources.Error70x70); return; } try { if (book.HasFatalError) //NB: we might not know yet... we don't fully load every book just to show its thumbnail { callback(Resources.Error70x70); return; } Image thumb; if (book.Storage.TryGetPremadeThumbnail(thumbnailOptions.FileName, out thumb)) { callback(thumb); return; } var dom = book.GetPreviewXmlDocumentForFirstPage(); if (dom == null) { callback(Resources.Error70x70); return; } string folderForCachingThumbnail; folderForCachingThumbnail = book.Storage.FolderPath; _thumbnailProvider.GetThumbnail(folderForCachingThumbnail, book.Storage.Key, dom, thumbnailOptions, callback, errorCallback, async); } catch (Exception err) { callback(Resources.Error70x70); Debug.Fail(err.Message); } }
/// <summary> /// Generates a thumbnail suitable for sharing on Facebook /// </summary> /// <param name="book"></param> /// <returns></returns> public static string GenerateSocialMediaSharingThumbnail(Book.Book book) { // 300 x 300 is picked so that FB will consistently generate a thumbnail that is: // * to the left of the link // * a fixed aspect ratio (happens to be square) // * has enough pixels to work with high res screens with devicePixelRatio up to 4. (FB Mobile App was showing it as 75 CSS pixels) // See BL-8406 int size = 300; var options = new HtmlThumbNailer.ThumbnailOptions { CenterImageUsingTransparentPadding = true, RequestId = new Guid(), Height = size, Width = size, }; options.FileName = $"thumbnail-{options.Width}x{options.Height}.png"; CreateThumbnailOfCoverImage(book, options); var thumbnailDestination = Path.Combine(book.FolderPath, options.FileName); return(thumbnailDestination); }
/// <summary> /// Will call either 'callback' or 'errorCallback' UNLESS the thumbnail is readonly, in which case it will do neither. /// </summary> /// <param name="book"></param> /// <param name="thumbnailOptions"></param> /// <param name="callback"></param> /// <param name="errorCallback"></param> public void RebuildThumbNailAsync(Book.Book book, HtmlThumbNailer.ThumbnailOptions thumbnailOptions, Action <BookInfo, Image> callback, Action <BookInfo, Exception> errorCallback) { RebuildThumbNail(book, thumbnailOptions, callback, errorCallback, true); }
/// <summary> /// Make a thumbnail image of a book's front cover. /// </summary> /// <param name="book"></param> /// <param name="height">Optional parameter. If unspecified, use defaults</param> public void MakeThumbnailOfCover(Book.Book book, int height = -1, Guid requestId = new Guid()) { HtmlThumbNailer.ThumbnailOptions options = GetCoverThumbnailOptions(height, requestId); RebuildThumbNailNow(book, options); }
public void MakeThumbnailOfCover(Book.Book book, int height, Control invokeTarget) { HtmlThumbNailer.ThumbnailOptions options = new HtmlThumbNailer.ThumbnailOptions { //since this is destined for HTML, it's much easier to handle if there is no pre-padding CenterImageUsingTransparentPadding = false }; if (height != -1) { options.Height = height; options.Width = -1; options.FileName = "thumbnail-" + height + ".png"; } // else use the defaults RebuildThumbNailNow(book, options); }