/// <summary> /// Calculates the bitmap dimensions and creates a new bitmap (if requested) with text drawn to /// according to the settings applied to this text renderer. /// </summary> /// <param name="isRequestingBitmapInfoOnly"> /// <para>Set to true to not produce a bitmap and measure what the bitmap size will be instead.</para> /// <para>Set to false to create a bitmap with text rendered to it.</para> /// </param> /// <returns> /// <para> /// Returns a result object with the requested bitmap data if flagged successful. /// If the "isRequestingBitmapInfoOnly" argument was set true, then the result object will only provide /// BitmapInfo and its Bitmap property will be null. /// </para> /// <para> /// Returns a failure result object if there was an error, in which case the the result's Message property /// would provide details as to what went wrong. /// </para> /// </returns> private TextBitmapDataResult AcquireBitmapData(bool isRequestingBitmapInfoOnly) { // Do not continue if there is no text to render. if (fSettings.Text.Length <= 0) { return(new TextBitmapDataResult("No text to render.")); } // Do not continue if not called on the main UI thread. // Note: Microsoft's WriteableBitmap.render() method used below can only be called on the main UI thread. if (System.Windows.Deployment.Current.Dispatcher.CheckAccess() == false) { return(new TextBitmapDataResult("Text can only be rendered on the main UI thread.")); } // Do not continue if Corona is currently synchronized with the rendering thread. // Note: Calling the WriteableBitmap.render() while the rendering thread is blocked will cause deadlock. if (Direct3DSurfaceAdapter.IsSynchronizedWithRenderingThread) { return(new TextBitmapDataResult("Cannot render text while Corona is synchronized with the rendering thread.")); } // Create a Xaml text control to be used to render text to a bitmap. var textBlock = new System.Windows.Controls.TextBlock(); textBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.White); textBlock.VerticalAlignment = System.Windows.VerticalAlignment.Top; textBlock.Text = fSettings.Text; // Set up the font. if (string.IsNullOrEmpty(fSettings.FontSettings.FamilyName) == false) { if (string.IsNullOrEmpty(fSettings.FontSettings.FilePath) == false) { System.IO.FileStream stream = null; try { stream = System.IO.File.OpenRead(fSettings.FontSettings.FilePath); textBlock.FontSource = new System.Windows.Documents.FontSource(stream); } catch (Exception) { } finally { if (stream != null) { try { stream.Close(); stream.Dispose(); } catch (Exception) { } } } } textBlock.FontFamily = new System.Windows.Media.FontFamily(fSettings.FontSettings.FamilyName); } textBlock.FontSize = fSettings.FontSettings.PointSize; textBlock.FontWeight = fSettings.FontSettings.IsBold ? System.Windows.FontWeights.Bold : System.Windows.FontWeights.Normal; textBlock.FontStyle = fSettings.FontSettings.IsItalic ? System.Windows.FontStyles.Italic : System.Windows.FontStyles.Normal; // Set up the horizontal alignment of the text. if (fSettings.HorizontalAlignment == WinRT.Interop.Graphics.HorizontalAlignment.Center) { textBlock.TextAlignment = System.Windows.TextAlignment.Center; } else if (fSettings.HorizontalAlignment == WinRT.Interop.Graphics.HorizontalAlignment.Right) { textBlock.TextAlignment = System.Windows.TextAlignment.Right; } else { textBlock.TextAlignment = System.Windows.TextAlignment.Left; } // Set up multiline text wrapping if enabled. if (fSettings.BlockWidth > 0) { // Enable text wrapping at the given pixel width. textBlock.TextWrapping = System.Windows.TextWrapping.Wrap; textBlock.MaxWidth = (double)fSettings.BlockWidth; textBlock.Width = (double)fSettings.BlockWidth; } else { // Disable text wrapping. textBlock.TextWrapping = System.Windows.TextWrapping.NoWrap; } if (fSettings.BlockHeight > 0) { textBlock.MaxHeight = (double)fSettings.BlockHeight; textBlock.Height = (double)fSettings.BlockHeight; } // Calculate a pixel width and height for the bitmap to render text to. int bitmapWidth = fSettings.BlockWidth; if (bitmapWidth <= 0) { bitmapWidth = (int)(Math.Ceiling(textBlock.ActualWidth) + 0.1); } if ((fSettings.ClipWidth > 0) && (bitmapWidth > fSettings.ClipWidth)) { bitmapWidth = fSettings.ClipWidth; } int bitmapHeight = fSettings.BlockHeight; if (bitmapHeight <= 0) { bitmapHeight = (int)(Math.Ceiling(textBlock.ActualHeight) + 0.1); } if ((fSettings.ClipHeight > 0) && (bitmapHeight > fSettings.ClipHeight)) { bitmapHeight = fSettings.ClipHeight; } // Stop here if the caller is only requesting the resulting bitmap's measurements. if (isRequestingBitmapInfoOnly) { var bitmapSettings = new WinRT.Interop.Graphics.BitmapSettings(); bitmapSettings.PixelFormat = WinRT.Interop.Graphics.PixelFormat.Grayscale; bitmapSettings.PremultipliedAlphaApplied = true; bitmapSettings.PixelWidth = bitmapWidth; bitmapSettings.PixelHeight = bitmapHeight; return(new TextBitmapDataResult(new WinRT.Interop.Graphics.BitmapInfo(bitmapSettings))); } // Determine if the text contains any visible/printable characters. bool hasPrintableCharacters = false; foreach (char nextCharacter in textBlock.Text.ToCharArray()) { if ((System.Char.IsWhiteSpace(nextCharacter) == false) && (System.Char.IsControl(nextCharacter) == false)) { hasPrintableCharacters = true; break; } } // If there is no text to render, then stop here and return an empty bitmap. // Note: This is a huge optimization. No point in rendering text below if you can't see the characters. if (hasPrintableCharacters == false) { var bitmapSettings = new WinRT.Interop.Graphics.BitmapSettings(); bitmapSettings.PixelFormat = WinRT.Interop.Graphics.PixelFormat.Grayscale; bitmapSettings.PremultipliedAlphaApplied = true; bitmapSettings.PixelWidth = bitmapWidth; bitmapSettings.PixelHeight = bitmapHeight; var emptyGrayscaleBitmap = new WinRT.Interop.Graphics.Bitmap(); emptyGrayscaleBitmap.FormatUsing(new WinRT.Interop.Graphics.BitmapInfo(bitmapSettings)); return(new TextBitmapDataResult(emptyGrayscaleBitmap)); } // If the text has been clipped, then shift the text within the bounds of the bitmap. System.Windows.Media.TranslateTransform transform = null; if (textBlock.ActualWidth > (double)bitmapWidth) { transform = new System.Windows.Media.TranslateTransform(); if (textBlock.TextAlignment == System.Windows.TextAlignment.Right) { transform.X = bitmapWidth - textBlock.ActualWidth; } else if (textBlock.TextAlignment == System.Windows.TextAlignment.Center) { transform.X = (bitmapWidth - textBlock.ActualWidth) / 2.0; } } // Render the text to a 32-bit color bitmap. System.Windows.Media.Imaging.WriteableBitmap writeableBitmap = null; try { // Create the bitmap. writeableBitmap = new System.Windows.Media.Imaging.WriteableBitmap(bitmapWidth, bitmapHeight); // Create a rectangle used to render a black background. var backgroundColor = System.Windows.Media.Colors.Black; var backgroundRectangle = new System.Windows.Shapes.Rectangle(); backgroundRectangle.Width = bitmapWidth; backgroundRectangle.Height = bitmapHeight; backgroundRectangle.Fill = new System.Windows.Media.SolidColorBrush(backgroundColor); // Convert the background color object to a 32-bit integer. // To be compared with the bitmap's integer pixel array below. int backgroundColorAsInt32 = ((int)backgroundColor.B) | ((int)backgroundColor.G << 8) | ((int)backgroundColor.R << 16) | ((int)backgroundColor.A << 24); // Attempt to render text to the bitmap. // Note: There is a bug on Windows Phone where WriteableBitmap sometimes fails to render/inavlidate // content if a Xaml DrawingSurfaceBackgroundGrid is being used by the application. // If this happens, then we must attempt to render again. There is no other known work-around. bool wasDrawn = false; for (int renderAttempt = 1; renderAttempt <= 3; renderAttempt++) { // Notify the owner(s) that we're about to render to the bitmap. if (this.Rendering != null) { this.Rendering.Invoke(this, new DotNetWriteableBitmapEventArgs(writeableBitmap)); } // Render the text and its black background to the bitmap. writeableBitmap.Render(backgroundRectangle, null); writeableBitmap.Render(textBlock, transform); writeableBitmap.Invalidate(); // --- Verify that the above text was successfully drawn to the bitmap. --- // First, check that the black rectangle was drawn to the bitmap. // This is a fast check because we only need to read one pixel in the bitmap's top-left corner. if (writeableBitmap.Pixels[0] != backgroundColorAsInt32) { continue; } // Next, check that text was drawn to the bitmap. if (hasPrintableCharacters) { // Traverse all pixels in the bitmap until we find 1 pixel that does not match the background color. for (int pixelIndex = writeableBitmap.Pixels.Length - 1; pixelIndex >= 0; pixelIndex--) { if (writeableBitmap.Pixels[pixelIndex] != backgroundColorAsInt32) { wasDrawn = true; break; } } } else { // The given text does not contain any visible characters. So, we're done. wasDrawn = true; } // Stop now if we've successfully drawn to the bitmap. if (wasDrawn) { break; } } // Log a failure if we were unable to render text. // Note: This still returns a bitmap. Should we? if (wasDrawn == false) { String message = "Failed to create a bitmap for text: \"" + textBlock.Text + "\"\r\n"; Corona.WinRT.Interop.Logging.LoggingServices.Log(message); } } catch (Exception ex) { return(new TextBitmapDataResult(ex.Message)); } // Convert the 32-bit color bitmap to 8-bit grayscale. var rgbaBitmap = DotNetBitmap.From(writeableBitmap); var bitmapConveter = new WinRT.Interop.Graphics.BitmapConverter(); bitmapConveter.PixelFormat = WinRT.Interop.Graphics.PixelFormat.Grayscale; var grayscaleBitmap = bitmapConveter.CreateBitmapFrom(rgbaBitmap); rgbaBitmap.ReleaseByteBuffer(); if (grayscaleBitmap == null) { return(new TextBitmapDataResult("Failed to convert the 32-bit color text to a grayscale bitmap.")); } // Return the text as a grayscaled bitmap. return(new TextBitmapDataResult(grayscaleBitmap)); }
/// <summary>Decodes the given image file and returns an uncompressed bitmap.</summary> /// <param name="filePath">The name and path of the image file to be decoded.</param> /// <returns> /// <para>Returns the result of the image decoding operation.</para> /// <para> /// If the result object's HasSucceeded property is set true, then this decoder succeeded in decoding /// the given image file and the result object's Bitmap property would provide the bitmap that was produced. /// </para> /// <para> /// If the result object's HasSucceeded property is false, then this decoder failed to decode the /// given image file. The reason why would then be provided by the result object's Message property. /// </para> /// </returns> public WinRT.Interop.Graphics.BitmapResult DecodeFromFile(string filePath) { // Validate the given image file path. if (String.IsNullOrEmpty(filePath)) { return(WinRT.Interop.Graphics.BitmapResult.FailedWith("Image decoder given a null or empty string.")); } // Check that the given file exists and if its an external or embedded resource file. bool isExternalFile = true; var resourceServices = Corona.WinRT.Interop.Storage.DotNetResourceServices.Instance; if (System.IO.File.Exists(filePath) == false) { if (resourceServices.ContainsFile(filePath) == false) { String message = String.Format("Image file '{0}' not found.", filePath); return(WinRT.Interop.Graphics.BitmapResult.FailedWith(message)); } isExternalFile = false; } // Ensure that the preferred pixel format is in the supported pixel format set. fSettings.SupportedPixelFormats.Add(fSettings.PreferredPixelFormat); Corona.WinRT.Interop.Graphics.IBitmap bitmap = null; System.IO.Stream stream = null; try { if (isExternalFile) { stream = System.IO.File.OpenRead(filePath); } else { stream = resourceServices.OpenFile(filePath); } var writeableBitmap = new System.Windows.Media.Imaging.WriteableBitmap(0, 0); writeableBitmap.SetSource(stream); bitmap = DotNetBitmap.From(writeableBitmap); writeableBitmap = null; // Convert the loaded bitmap if it does not meet the decoder's requirements. if ((fSettings.SupportedPixelFormats.Contains(bitmap.Info.PixelFormat) == false) || (bitmap.Info.PixelWidth > fSettings.MaxPixelWidth) || (bitmap.Info.PixelHeight > fSettings.MaxPixelHeight)) { var converter = new WinRT.Interop.Graphics.BitmapConverter(); converter.CopySettingsFrom(this); // if (converter.CanConvert(bitmap)) // { // converter.Convert(bitmap); // } // else // { var convertedBitmap = converter.CreateBitmapFrom(bitmap); bitmap.ReleaseByteBuffer(); bitmap = convertedBitmap; // } } } catch (Exception ex) { if (bitmap != null) { try { bitmap.ReleaseByteBuffer(); } catch (Exception) { } } return(WinRT.Interop.Graphics.BitmapResult.FailedWith(ex.Message)); } finally { if (stream != null) { try { stream.Close(); stream.Dispose(); } catch (Exception) { } } } if (bitmap == null) { return(WinRT.Interop.Graphics.BitmapResult.FailedWith("Failed to load bitmap")); } return(WinRT.Interop.Graphics.BitmapResult.SucceededWith(bitmap)); }