using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using FastReport.Utils; namespace FastReport.Export.Image { /// /// Specifies the image export format. /// public enum ImageExportFormat { /// /// Specifies the .bmp format. /// Bmp, /// /// Specifies the .png format. /// Png, /// /// Specifies the .jpg format. /// Jpeg, /// /// Specifies the .gif format. /// Gif, /// /// Specifies the .tif format. /// Tiff, /// /// Specifies the .emf format. /// Metafile } /// /// Represents the image export filter. /// public partial class ImageExport : ExportBase { private ImageExportFormat imageFormat; private bool separateFiles; private int resolutionX; private int resolutionY; private int jpegQuality; private bool multiFrameTiff; private bool monochromeTiff; private EncoderValue monochromeTiffCompression; private System.Drawing.Image masterTiffImage; private System.Drawing.Image bigImage; private Graphics bigGraphics; private float curOriginY; private bool firstPage; private int paddingNonSeparatePages; private int pageNumber; private System.Drawing.Image image; private Graphics g; private int height; private int width; private int widthK; private string fileSuffix; private float zoomX; private float zoomY; private System.Drawing.Drawing2D.GraphicsState state; private string imageExtensionFormat; private string documentTitle; private bool saveStreams; const float DIVIDER = 0.75f; const float PAGE_DIVIDER = 2.8346400000000003f; // mm to point #region Properties /// /// Gets or sets the image format. /// public ImageExportFormat ImageFormat { get { return imageFormat; } set { imageFormat = value; } } /// /// Gets or sets a value that determines whether to generate separate image file /// for each exported page. /// /// /// If this property is set to false, the export filter will produce one big image /// containing all exported pages. Be careful using this property with a big report /// because it may produce out of memory error. /// And also when using Memory Stream and the value is true, an exception will be thrown. /// public bool SeparateFiles { get { return separateFiles; } set { separateFiles = value; } } /// /// Gets or sets image resolution, in dpi. /// /// /// By default this property is set to 96 dpi. Use bigger values (300-600 dpi) /// if you going to print the exported images. /// public int Resolution { get { return resolutionX; } set { resolutionX = value; resolutionY = value; } } /// /// Gets or sets horizontal image resolution, in dpi. /// /// /// Separate horizontal and vertical resolution is used when exporting to TIFF. In other /// cases, use the property instead. /// public int ResolutionX { get { return resolutionX; } set { resolutionX = value; } } /// /// Gets or sets vertical image resolution, in dpi. /// /// /// Separate horizontal and vertical resolution is used when exporting to TIFF. In other /// cases, use the property instead. /// public int ResolutionY { get { return resolutionY; } set { resolutionY = value; } } /// /// Gets or sets the jpg image quality. /// /// /// This property is used if is set to Jpeg. By default /// it is set to 100. Use lesser value to decrease the jpg file size. /// public int JpegQuality { get { return jpegQuality; } set { jpegQuality = value; } } /// /// Gets or sets the value determines whether to produce multi-frame tiff file. /// public bool MultiFrameTiff { get { return multiFrameTiff; } set { multiFrameTiff = value; } } /// /// Gets or sets a value that determines whether the Tiff export must produce monochrome image. /// /// /// Monochrome tiff image is compressed using the compression method specified in the /// property. /// public bool MonochromeTiff { get { return monochromeTiff; } set { monochromeTiff = value; } } /// /// Gets or sets the compression method for a monochrome TIFF image. /// /// /// This property is used only when exporting to TIFF image, and the property /// is set to true. /// The valid values for this property are: EncoderValue.CompressionNone, /// EncoderValue.CompressionLZW, EncoderValue.CompressionRle, /// EncoderValue.CompressionCCITT3, EncoderValue.CompressionCCITT4. /// The default compression method is CCITT4. /// public EncoderValue MonochromeTiffCompression { get { return monochromeTiffCompression; } set { monochromeTiffCompression = value; } } /// /// Sets padding in non separate pages /// public int PaddingNonSeparatePages { get { return paddingNonSeparatePages; } set { paddingNonSeparatePages = value; } } private bool IsMultiFrameTiff { get { return ImageFormat == ImageExportFormat.Tiff && MultiFrameTiff; } } /// /// Enable or disable saving streams in GeneratedStreams collection. /// public bool SaveStreams { get { return saveStreams; } set { saveStreams = value; } } #endregion #region Private Methods private System.Drawing.Image CreateImage(int width, int height, string suffix) { widthK = width; if (ImageFormat == ImageExportFormat.Metafile) return CreateMetafile(suffix); return new Bitmap(width, height); } private void GeneratedUpdate(string filename, Stream stream) { int i = GeneratedFiles.IndexOf(filename); if (i == -1) { GeneratedFiles.Add(filename); GeneratedStreams.Add(stream); } else { GeneratedStreams[i] = stream; } } private System.Drawing.Image CreateMetafile(string suffix) { string extension = Path.GetExtension(FileName); string targetFileName = Path.ChangeExtension(FileName, suffix + extension); System.Drawing.Image image; using (Bitmap bmp = new Bitmap(1, 1)) using (Graphics g = Graphics.FromImage(bmp)) { IntPtr hdc = g.GetHdc(); if (suffix == "") image = new Metafile(Stream, hdc); else { image = new Metafile(targetFileName, hdc); if (!GeneratedFiles.Contains(targetFileName)) GeneratedFiles.Add(targetFileName); } g.ReleaseHdc(hdc); } return image; } private Bitmap ConvertToBitonal(Bitmap original) { Bitmap source = null; // If original bitmap is not already in 32 BPP, ARGB format, then convert if (original.PixelFormat != PixelFormat.Format32bppArgb) { source = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb); source.SetResolution(original.HorizontalResolution, original.VerticalResolution); using (Graphics g = Graphics.FromImage(source)) { g.DrawImageUnscaled(original, 0, 0); } } else { source = original; } // Lock source bitmap in memory BitmapData sourceData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); // Copy image data to binary array int imageSize = sourceData.Stride * sourceData.Height; byte[] sourceBuffer = new byte[imageSize]; Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize); // Unlock source bitmap source.UnlockBits(sourceData); // Create destination bitmap Bitmap destination = new Bitmap(source.Width, source.Height, PixelFormat.Format1bppIndexed); // Lock destination bitmap in memory BitmapData destinationData = destination.LockBits(new Rectangle(0, 0, destination.Width, destination.Height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed); // Create destination buffer imageSize = destinationData.Stride * destinationData.Height; byte[] destinationBuffer = new byte[imageSize]; int sourceIndex = 0; int destinationIndex = 0; int pixelTotal = 0; byte destinationValue = 0; int pixelValue = 128; int height = source.Height; int width = source.Width; int threshold = 500; // Iterate lines for (int y = 0; y < height; y++) { sourceIndex = y * sourceData.Stride; destinationIndex = y * destinationData.Stride; destinationValue = 0; pixelValue = 128; // Iterate pixels for (int x = 0; x < width; x++) { // Compute pixel brightness (i.e. total of Red, Green, and Blue values) pixelTotal = sourceBuffer[sourceIndex + 1] + sourceBuffer[sourceIndex + 2] + sourceBuffer[sourceIndex + 3]; if (pixelTotal > threshold) { destinationValue += (byte)pixelValue; } if (pixelValue == 1) { destinationBuffer[destinationIndex] = destinationValue; destinationIndex++; destinationValue = 0; pixelValue = 128; } else { pixelValue >>= 1; } sourceIndex += 4; } if (pixelValue != 128) { destinationBuffer[destinationIndex] = destinationValue; } } // Copy binary image data to destination bitmap Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, imageSize); // Unlock destination bitmap destination.UnlockBits(destinationData); // Dispose of source if not originally supplied bitmap if (source != original) { source.Dispose(); } // Return destination.SetResolution(ResolutionX, ResolutionY); return destination; } private void SaveImage(System.Drawing.Image image, string suffix) { // store the resolution in output file. // Call this method after actual draw because it may affect drawing the text if (image is Bitmap) (image as Bitmap).SetResolution(ResolutionX, ResolutionY); if (IsMultiFrameTiff) { // select the image encoder ImageCodecInfo info = ExportUtils.GetCodec("image/tiff"); EncoderParameters ep = new EncoderParameters(2); ep.Param[0] = new EncoderParameter(Encoder.Compression, MonochromeTiff ? (long)MonochromeTiffCompression : (long)EncoderValue.CompressionLZW); if (image == masterTiffImage) { // save the master bitmap if (MonochromeTiff) masterTiffImage = ConvertToBitonal(image as Bitmap); ep.Param[1] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.MultiFrame); masterTiffImage.Save(Stream, info, ep); } else { // save the frame if (MonochromeTiff) { System.Drawing.Image oldImage = image; image = ConvertToBitonal(image as Bitmap); oldImage.Dispose(); } ep.Param[1] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.FrameDimensionPage); masterTiffImage.SaveAdd(image, ep); } } else if (ImageFormat != ImageExportFormat.Metafile) { Stream stream; string targetFileName; if (saveStreams) { targetFileName = Path.ChangeExtension(documentTitle + suffix, imageExtensionFormat); stream = new MemoryStream(); } else { string extension = Path.GetExtension(FileName); targetFileName = Path.ChangeExtension(FileName, suffix + extension); // empty suffix means that we should use the Stream that was created in the ExportBase stream = suffix == "" ? Stream : new FileStream(targetFileName, FileMode.Create); if (suffix != "") { GeneratedFiles.Add(targetFileName); } } if (ImageFormat == ImageExportFormat.Jpeg) { ExportUtils.SaveJpeg(image, stream, JpegQuality); } else if (ImageFormat == ImageExportFormat.Tiff && MonochromeTiff) { // handle monochrome tiff separately ImageCodecInfo info = ExportUtils.GetCodec("image/tiff"); EncoderParameters ep = new EncoderParameters(); ep.Param[0] = new EncoderParameter(Encoder.Compression, (long)MonochromeTiffCompression); using (Bitmap bwImage = ConvertToBitonal(image as Bitmap)) { bwImage.Save(stream, info, ep); } } else { ImageFormat format = System.Drawing.Imaging.ImageFormat.Bmp; switch (ImageFormat) { case ImageExportFormat.Gif: format = System.Drawing.Imaging.ImageFormat.Gif; break; case ImageExportFormat.Png: format = System.Drawing.Imaging.ImageFormat.Png; break; case ImageExportFormat.Tiff: format = System.Drawing.Imaging.ImageFormat.Tiff; break; } image.Save(stream, format); } if (saveStreams) GeneratedUpdate(targetFileName, stream); else if (suffix != "") stream.Dispose(); } if (image != masterTiffImage) image.Dispose(); } #endregion #region Protected Methods /// protected override string GetFileFilter() { string filter = ImageFormat.ToString(); return Res.Get("FileFilters," + filter + "File"); } /// protected override void Start() { base.Start(); //init SeparateFiles = Stream is MemoryStream ? false : SeparateFiles; GeneratedStreams = new List(); pageNumber = 0; height = 0; width = 0; image = null; g = null; zoomX = 1; zoomY = 1; state = null; curOriginY = 0; firstPage = true; if (saveStreams) { imageExtensionFormat = ImageFormat.ToString(); separateFiles = true; documentTitle = (!String.IsNullOrEmpty(Report.ReportInfo.Name) ? Report.ReportInfo.Name : ""); } if (!SeparateFiles && !IsMultiFrameTiff) { // create one big image. To do this, calculate max width and sum of pages height float w = 0; float h = 0; foreach (int pageNo in Pages) { SizeF size = Report.PreparedPages.GetPageSize(pageNo); if (size.Width > w) w = size.Width; h += size.Height + paddingNonSeparatePages * 2; } w += paddingNonSeparatePages * 2; bigImage = CreateImage((int)(w * ResolutionX / 96f), (int)(h * ResolutionY / 96f), ""); bigGraphics = Graphics.FromImage(bigImage); bigGraphics.Clear(Color.Transparent); } pageNumber = 0; } /// protected override void ExportPageBegin(ReportPage page) { base.ExportPageBegin(page); zoomX = ResolutionX / 96f; zoomY = ResolutionY / 96f; width = (int)(ExportUtils.GetPageWidth(page) * Units.Millimeters * zoomX); height = (int)(ExportUtils.GetPageHeight(page) * Units.Millimeters * zoomY); int suffixDigits = Pages[Pages.Length - 1].ToString().Length; fileSuffix = firstPage ? "" : (pageNumber + 1).ToString("".PadLeft(suffixDigits, '0')); if (SeparateFiles || IsMultiFrameTiff) { image = CreateImage(width, height, fileSuffix); if (IsMultiFrameTiff && masterTiffImage == null) masterTiffImage = image; } else image = bigImage; if (bigGraphics != null) g = bigGraphics; else g = Graphics.FromImage(image); state = g.Save(); g.FillRegion(Brushes.Transparent, new Region(new RectangleF(0, curOriginY, width, height))); if (bigImage != null && curOriginY + height * 2 > bigImage.Height) page.Fill.Draw(new FRPaintEventArgs(g, 1, 1, Report.GraphicCache), new RectangleF(0, curOriginY, widthK, bigImage.Height - curOriginY)); else page.Fill.Draw(new FRPaintEventArgs(g, 1, 1, Report.GraphicCache), new RectangleF(0, curOriginY, widthK, height + paddingNonSeparatePages * 2)); if (image == bigImage) { if (ImageFormat != ImageExportFormat.Metafile) g.TranslateTransform(image.Width / 2 - width / 2 + page.LeftMargin * Units.Millimeters * zoomX, curOriginY + paddingNonSeparatePages + page.TopMargin * Units.Millimeters * zoomY); else g.TranslateTransform(widthK / 2 - width / 2 + page.LeftMargin * Units.Millimeters * zoomX, curOriginY + paddingNonSeparatePages + page.TopMargin * Units.Millimeters * zoomY); } else g.TranslateTransform(page.LeftMargin * Units.Millimeters * zoomX, page.TopMargin * Units.Millimeters * zoomY); g.ScaleTransform(1, zoomY / zoomX); // export bottom watermark if (page.Watermark.Enabled && !page.Watermark.ShowImageOnTop) AddImageWatermark(page); if (page.Watermark.Enabled && !page.Watermark.ShowTextOnTop) AddTextWatermark(page); // page borders if (page.Border.Lines != BorderLines.None) { using (TextObject pageBorder = new TextObject()) { pageBorder.Border = page.Border; pageBorder.Left = 0; pageBorder.Top = 0; pageBorder.Width = (ExportUtils.GetPageWidth(page) - page.LeftMargin - page.RightMargin) * PAGE_DIVIDER / DIVIDER; pageBorder.Height = (ExportUtils.GetPageHeight(page) - page.TopMargin - page.BottomMargin) * PAGE_DIVIDER / DIVIDER; ExportObj(pageBorder); } } } /// protected override void ExportBand(BandBase band) { base.ExportBand(band); ExportObj(band); foreach (Base c in band.ForEachAllConvectedObjects(this)) { if (!(c is Table.TableColumn || c is Table.TableCell || c is Table.TableRow)) ExportObj(c); } } private void ExportObj(Base obj) { if (obj is ReportComponentBase && (obj as ReportComponentBase).Exportable) (obj as ReportComponentBase).Draw(new FRPaintEventArgs(g, zoomX, zoomX, Report.GraphicCache)); } /// protected override void ExportPageEnd(ReportPage page) { // export top watermark if (page.Watermark.Enabled && page.Watermark.ShowImageOnTop) AddImageWatermark(page); if (page.Watermark.Enabled && page.Watermark.ShowTextOnTop) AddTextWatermark(page); g.Restore(state); if (g != bigGraphics) g.Dispose(); if (SeparateFiles || IsMultiFrameTiff) SaveImage(image, fileSuffix); else curOriginY += height + paddingNonSeparatePages * 2; firstPage = false; pageNumber++; } private void AddImageWatermark(ReportPage page) { page.Watermark.DrawImage(new FRPaintEventArgs(g, zoomX, zoomX, Report.GraphicCache), new RectangleF(-page.LeftMargin * Units.Millimeters, -page.TopMargin * Units.Millimeters, width / zoomX, height / zoomY), page.Report, false); } private void AddTextWatermark(ReportPage page) { if (string.IsNullOrEmpty(page.Watermark.Text)) return; page.Watermark.DrawText(new FRPaintEventArgs(g, zoomX, zoomX, Report.GraphicCache), new RectangleF(-page.LeftMargin * Units.Millimeters, -page.TopMargin * Units.Millimeters, width / zoomX, height / zoomY), page.Report, false); } /// protected override void Finish() { if (IsMultiFrameTiff) { // close the file. EncoderParameters ep = new EncoderParameters(1); ep.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush); masterTiffImage.SaveAdd(ep); } else if (!SeparateFiles) { bigGraphics.Dispose(); bigGraphics = null; SaveImage(bigImage, ""); } if (masterTiffImage != null) { masterTiffImage.Dispose(); masterTiffImage = null; } } #endregion #region Public Methods /// public override void Serialize(FRWriter writer) { base.Serialize(writer); writer.WriteValue("ImageFormat", ImageFormat); writer.WriteBool("SeparateFiles", SeparateFiles); writer.WriteInt("ResolutionX", ResolutionX); writer.WriteInt("ResolutionY", ResolutionY); writer.WriteInt("JpegQuality", JpegQuality); writer.WriteBool("MultiFrameTiff", MultiFrameTiff); writer.WriteBool("MonochromeTiff", MonochromeTiff); } #endregion /// /// Initializes a new instance of the class. /// public ImageExport() { paddingNonSeparatePages = 0; fileSuffix = String.Empty; HasMultipleFiles = true; imageFormat = ImageExportFormat.Jpeg; separateFiles = true; Resolution = 96; jpegQuality = 100; monochromeTiffCompression = EncoderValue.CompressionCCITT4; saveStreams = false; } } }