I am displaying images and video as the thumbnail in the view page. Whenever a user uploads image or video I am saving URL into DB and files into the folder. While displaying I am resizing image size on the server side to reduce the page load time. I am able to compress the images and but not the videos. How to resize or compress the video same as the image. At present videos are not displaying because using the same logic to retrieve both image and videos.
View :
Database:
ImageHandler.ashx.cs :
namespace ResoucesProject
{
public class ImageHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
var fileName = ConfigurationManager.AppSettings["BasePath"] + context.Request.QueryString["file"];
var filePath = context.Server.MapPath(fileName);
var fileWidth = 300;
if (!string.IsNullOrEmpty(context.Request.QueryString["width"]))
{
int tempWidth;
if (int.TryParse(context.Request.QueryString["width"], out tempWidth) && tempWidth < 3000) // set a max limit so people don't request huge files
fileWidth = tempWidth;
}
var fileHeight = 300;
if (!string.IsNullOrEmpty(context.Request.QueryString["height"]))
{
int tempHeight;
if (int.TryParse(context.Request.QueryString["height"], out tempHeight) && tempHeight < 3000) // set a max limit so people don't request huge files
fileHeight = tempHeight;
}
context.Response.AddHeader("content-disposition",
string.Format("attachment; filename={0}", fileName));
if (File.Exists(filePath))
{
var buffer = GetResizedImage(filePath, fileWidth, fileHeight);
if (buffer == null)
{
return;
}
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.End();
byte[] bytes = File.ReadAllBytes(filePath);
context.Response.BinaryWrite(bytes);
}
else
{
throw new HttpException(404, "Invalid photo name.");
}
}
private static byte[] GetResizedImage(string path, int width, int height)
{
try
{
var imgIn = new Bitmap(path);
double y = imgIn.Height;
double x = imgIn.Width;
double factor = 1;
if (width > 0)
{
factor = width / x;
}
else if (height > 0)
{
factor = height / y;
}
var outStream = new MemoryStream();
var imgOut = new Bitmap((int)(x * factor), (int)(y * factor));
// Set DPI of image (xDpi, yDpi)
imgOut.SetResolution(72, 72);
var g = Graphics.FromImage(imgOut);
g.Clear(Color.White);
g.DrawImage(imgIn, new Rectangle(0, 0, (int)(factor * x), (int)(factor * y)),
new Rectangle(0, 0, (int)x, (int)y), GraphicsUnit.Pixel);
imgOut.Save(outStream, GetImageFormat(path));
return outStream.ToArray();
}
catch (ArgumentException e)
{
Console.WriteLine(e.Message);
return null;
}
}
private static ImageFormat GetImageFormat(string path)
{
switch (Path.GetExtension(path))
{
case ".bmp": return ImageFormat.Bmp;
case ".gif": return ImageFormat.Gif;
case ".jpg": return ImageFormat.Jpeg;
case ".png": return ImageFormat.Png;
}
return ImageFormat.Jpeg;
}
}
}
View :
#foreach (var item in Model)
{
<tr>
<td>
<img src="~/ImageHandler.ashx?file=#Html.DisplayFor(modelItem =>item.image_url)&width=100&height=100" style="width:100px; height:100px;" />
</td>
</tr>
}
#foreach (var item in Model)
{
<tr>
<td>
<img src="~/ImageHandler.ashx?file=#Html.DisplayFor(modelItem =>item.image_url)&width=100&height=100" style="width:100px; height:100px;" />
</td>
</tr>
}
Related
I am trying to create code to export out the images within a PDF using iText Version 7.19. I'm having some issues with Flate encoded images. All the Flate encoded images from the Microsoft free book I'm using as an example (see Moving to Microsoft Visual Studio 2010) always coming out pink and depending upon how I try to copy the bytes they can come out distorted.
If I attempt to copy all the image bytes at once (see the SaveFlateEncodedImage2 method in the code below), they come out distorted like this one:
If I attempt to copy them row by row (see the SaveFlateEncodedImage method in the code below), they are pink like this one
Here is the code that I'm using to export them:
using iText.Kernel;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Filters;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace ITextPdfStuff
{
public class MyPdfImageExtractor
{
private readonly string _pdfFileName;
public MyPdfImageExtractor(string pdfFileName)
{
_pdfFileName = pdfFileName;
}
public void ExtractToDirectory(string directoryName)
{
using (var reader = new PdfReader(_pdfFileName))
{
// Avoid iText.Kernel.Crypto.BadPasswordException: https://stackoverflow.com/a/48065052/97803
reader.SetUnethicalReading(true);
using (var pdfDoc = new PdfDocument(reader))
{
ExtractImagesOnAllPages(pdfDoc, directoryName);
}
}
}
private void ExtractImagesOnAllPages(PdfDocument pdfDoc, string directoryName)
{
Console.WriteLine($"Number of pdf {pdfDoc.GetNumberOfPdfObjects()} objects");
// Extract objects https://itextpdf.com/en/resources/examples/itext-7/extracting-objects-pdf
for (int objNumber = 1; objNumber <= pdfDoc.GetNumberOfPdfObjects(); objNumber++)
{
PdfObject currentObject = pdfDoc.GetPdfObject(objNumber);
if (currentObject != null && currentObject.IsStream())
{
try
{
ExtractImagesOneImage(currentObject as PdfStream, Path.Combine(directoryName, $"image{objNumber}.png"));
}
catch (Exception ex)
{
Console.WriteLine($"Object number {objNumber} is NOT an image! -- error: {ex.Message}");
}
}
}
}
private void ExtractImagesOneImage(PdfStream someStream, string fileName)
{
var pdfDict = (PdfDictionary)someStream;
string subType = pdfDict.Get(PdfName.Subtype)?.ToString() ?? string.Empty;
bool isImage = subType == "/Image";
if (isImage == false)
return;
bool decoded = false;
string filter = pdfDict.Get(PdfName.Filter).ToString();
if (filter == "/FlateDecode")
{
SaveFlateEncodedImage(fileName, pdfDict, someStream.GetBytes(false));
}
else
{
byte[] imgData;
try
{
imgData = someStream.GetBytes(decoded);
}
catch (PdfException ex)
{
imgData = someStream.GetBytes(!decoded);
}
SaveNormalImage(fileName, imgData);
}
}
private void SaveNormalImage(string fileName, byte[] imgData)
{
using (var memStream = new System.IO.MemoryStream(imgData))
using (var image = System.Drawing.Image.FromStream(memStream))
{
image.Save(fileName, ImageFormat.Png);
Console.WriteLine($"{Path.GetFileName(fileName)}");
}
}
private void SaveFlateEncodedImage(string fileName, PdfDictionary pdfDict, byte[] imgData)
{
int width = int.Parse(pdfDict.Get(PdfName.Width).ToString());
int height = int.Parse(pdfDict.Get(PdfName.Height).ToString());
int bpp = int.Parse(pdfDict.Get(PdfName.BitsPerComponent).ToString());
// Example that helped: https://stackoverflow.com/a/8517377/97803
PixelFormat pixelFormat;
switch (bpp)
{
case 1:
pixelFormat = PixelFormat.Format1bppIndexed;
break;
case 8:
pixelFormat = PixelFormat.Format8bppIndexed;
break;
case 24:
pixelFormat = PixelFormat.Format24bppRgb;
break;
default:
throw new Exception("Unknown pixel format " + bpp);
}
// .NET docs https://api.itextpdf.com/iText7/dotnet/7.1.9/classi_text_1_1_kernel_1_1_pdf_1_1_filters_1_1_flate_decode_strict_filter.html
// Java docs have more detail: https://api.itextpdf.com/iText7/java/7.1.7/com/itextpdf/kernel/pdf/filters/FlateDecodeFilter.html
imgData = FlateDecodeStrictFilter.FlateDecode(imgData, true);
// byte[] streamBytes = FlateDecodeStrictFilter.DecodePredictor(imgData, pdfDict);
// Copy the image one row at a time
using (var bmp = new Bitmap(width, height, pixelFormat))
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat);
int length = (int)Math.Ceiling(width * bpp / 8.0);
for (int i = 0; i < height; i++)
{
int offset = i * length;
int scanOffset = i * bmpData.Stride;
Marshal.Copy(imgData, offset, new IntPtr(bmpData.Scan0.ToInt64() + scanOffset), length);
}
bmp.UnlockBits(bmpData);
bmp.Save(fileName, ImageFormat.Png);
}
Console.WriteLine($"FlateDecode! {Path.GetFileName(fileName)}");
}
/// <summary>This method distorts the image badly</summary>
private void SaveFlateEncodedImage2(string fileName, PdfDictionary pdfDict, byte[] imgData)
{
int width = int.Parse(pdfDict.Get(PdfName.Width).ToString());
int height = int.Parse(pdfDict.Get(PdfName.Height).ToString());
int bpp = int.Parse(pdfDict.Get(PdfName.BitsPerComponent).ToString());
// Example that helped: https://stackoverflow.com/a/8517377/97803
PixelFormat pixelFormat;
switch (bpp)
{
case 1:
pixelFormat = PixelFormat.Format1bppIndexed;
break;
case 8:
pixelFormat = PixelFormat.Format8bppIndexed;
break;
case 24:
pixelFormat = PixelFormat.Format24bppRgb;
break;
default:
throw new Exception("Unknown pixel format " + bpp);
}
// .NET docs https://api.itextpdf.com/iText7/dotnet/7.1.9/classi_text_1_1_kernel_1_1_pdf_1_1_filters_1_1_flate_decode_strict_filter.html
// Java docs have more detail: https://api.itextpdf.com/iText7/java/7.1.7/com/itextpdf/kernel/pdf/filters/FlateDecodeFilter.html
imgData = FlateDecodeStrictFilter.FlateDecode(imgData, true);
// byte[] streamBytes = FlateDecodeStrictFilter.DecodePredictor(imgData, pdfDict);
// Copy the entire image in one go
using (var bmp = new Bitmap(width, height, pixelFormat))
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat);
Marshal.Copy(imgData, 0, bmpData.Scan0, imgData.Length);
bmp.UnlockBits(bmpData);
bmp.Save(fileName, ImageFormat.Png);
}
Console.WriteLine($"FlateDecode! {Path.GetFileName(fileName)}");
}
}
}
The code can be instantiated and called like this from within a .NET Core console application:
string existingFileName = #"c:\temp\ReallyLongBook1.pdf";
var imageExtractor = new MyPdfImageExtractor(existingFileName);
imageExtractor.ExtractToDirectory(#"c:\temp\images");
I'm running the following free Microsoft book through this code:
Moving to Microsoft Visual Studio 2010
The image in question is on page 10 and it's black and white (not pink).
I'm no PDF expert and I've been banging on this code for a couple of days now picking apart a number of examples to try to piece this together. Any help that would get me past my pink images, would be greatly appreciated.
-------Update Feb 4, 2020------
Here is the revised version after MKL's suggested changes. His change extracted more images than mine and produced proper looking images that appear in the book I mentioned above:
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Data;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
using iText.Kernel.Pdf.Xobject;
using System;
using System.Collections.Generic;
using System.IO;
namespace ITextPdfStuff
{
public class MyPdfImageExtractor
{
private readonly string _pdfFileName;
public MyPdfImageExtractor(string pdfFileName)
{
_pdfFileName = pdfFileName;
}
public void ExtractToDirectory(string directoryName)
{
using (var reader = new PdfReader(_pdfFileName))
{
// Avoid iText.Kernel.Crypto.BadPasswordException: https://stackoverflow.com/a/48065052/97803
reader.SetUnethicalReading(true);
using (var pdfDoc = new PdfDocument(reader))
{
ExtractImagesOnAllPages(pdfDoc, directoryName);
}
}
}
private void ExtractImagesOnAllPages(PdfDocument pdfDoc, string directoryName)
{
Console.WriteLine($"Number of pdf {pdfDoc.GetNumberOfPdfObjects()} objects");
IEventListener strategy = new ImageRenderListener(Path.Combine(directoryName, #"image{0}.{1}"));
PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy);
for (var i = 1; i <= pdfDoc.GetNumberOfPages(); i++)
{
parser.ProcessPageContent(pdfDoc.GetPage(i));
}
}
}
public class ImageRenderListener : IEventListener
{
public ImageRenderListener(string format)
{
this.format = format;
}
public void EventOccurred(IEventData data, EventType type)
{
if (data is ImageRenderInfo imageData)
{
try
{
PdfImageXObject imageObject = imageData.GetImage();
if (imageObject == null)
{
Console.WriteLine("Image could not be read.");
}
else
{
File.WriteAllBytes(string.Format(format, index++, imageObject.IdentifyImageFileExtension()), imageObject.GetImageBytes());
}
}
catch (Exception ex)
{
Console.WriteLine("Image could not be read: {0}.", ex.Message);
}
}
}
public ICollection<EventType> GetSupportedEvents()
{
return null;
}
string format;
int index = 0;
}
}
PDFs internally support a very flexible bitmap image format, in particular as far as different color spaces are concerned.
iText in its parsing API supports export of a subset thereof, essentially the subset of images that easily can be exported as regular JPEGs or PNGs.
Thus, it makes sense to try and export using the iText parsing API first. You can do that as follows:
Directory.CreateDirectory(#"extract\");
using (PdfReader reader = new PdfReader(#"Moving to Microsoft Visual Studio 2010 ebook.pdf"))
using (PdfDocument pdfDocument = new PdfDocument(reader))
{
IEventListener strategy = new ImageRenderListener(#"extract\Moving to Microsoft Visual Studio 2010 ebook-i7-{0}.{1}");
PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy);
for (var i = 1; i <= pdfDocument.GetNumberOfPages(); i++)
{
parser.ProcessPageContent(pdfDocument.GetPage(i));
}
}
with the helper class ImageRenderListener:
public class ImageRenderListener : IEventListener
{
public ImageRenderListener(string format)
{
this.format = format;
}
public void EventOccurred(IEventData data, EventType type)
{
if (data is ImageRenderInfo imageData)
{
try
{
PdfImageXObject imageObject = imageData.GetImage();
if (imageObject == null)
{
Console.WriteLine("Image could not be read.");
}
else
{
File.WriteAllBytes(string.Format(format, index++, imageObject.IdentifyImageFileExtension()), imageObject.GetImageBytes());
}
}
catch (Exception ex)
{
Console.WriteLine("Image could not be read: {0}.", ex.Message);
}
}
}
public ICollection<EventType> GetSupportedEvents()
{
return null;
}
string format;
int index = 0;
}
In case of your example document it exports nearly 400 images successfully, among them your example image above:
But there also are less than 30 images it cannot export, on standard out you'll find "Image could not be read: The color space /DeviceN is not supported.."
I am trying to open a print preview dialog and Print all the images. My problem is when I am printing the image, the width doesn't seem to be aligning with printer paper and the image is getting cut. How can I print the image on the paper without cutting it. Please help.
Please see Pdoc_PrintPage event method. I am using e.Graphics.DrawImageUnscaled is it the right overload that I am using. I debug my code put in the values.
private void PrintAllCharts(Dictionary<LightningChartUserControl, string> charts)
{
try
{
if (PrinterSettings.InstalledPrinters.Count == 0)
{
Xceed.Wpf.Toolkit.MessageBox.Show(System.Windows.Application.Current.TryFindResource("SetUpPrinter").ToString(),
"Default printer not found", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
string FilePathWithoutFileName = string.Empty;
DirectoryInfo info = Directory.CreateDirectory(#"C:\TempCharts");
for (int i = 0; i < charts.Count; i++)
{
KeyValuePair<LightningChartUserControl, string> kp = charts.ElementAt(i);
FilePathWithoutFileName = info.FullName;
string FullPath = string.Format("{0}/Chart{1}.png", FilePathWithoutFileName, i.ToString());
kp.Key.Chart.SaveToFile(FullPath);
}
var files = Directory.GetFiles(FilePathWithoutFileName);
using(var pdoc=new PrintDocument())
{
using(var pdi=new System.Windows.Forms.PrintDialog { Document = pdoc, UseEXDialog = true })
{
if (pdi.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
pdoc.PrinterSettings = pdi.PrinterSettings;
pdoc.PrintPage += Pdoc_PrintPage;
foreach(var file in files)
{
pdoc.DocumentName = file;
pdoc.Print();
}
}
}
}
PrinterSettings settings = new PrinterSettings();
Xceed.Wpf.Toolkit.MessageBox.Show(string.Format(System.Windows.Application.Current.TryFindResource("PrintSuccessful").ToString(),settings.PrinterName),
"Print Successful", MessageBoxButton.OK);
foreach(FileInfo file in info.GetFiles())
{
file.Delete();
}
foreach (DirectoryInfo dir in info.GetDirectories())
{
dir.Delete(true);
}
}
catch (Exception ex)
{
SystemDebugLogLogger.LogError(ex);
}
}
private void Pdoc_PrintPage(object sender, PrintPageEventArgs e)
{
string file = ((PrintDocument)sender).DocumentName;
System.Drawing.Image img = System.Drawing.Image.FromFile(file);
//e.Graphics.DrawImage(img, e.MarginBounds);
//e.Graphics.DrawImageUnscaled(img, e.MarginBounds);
var hei = img.Height; //509
var wid = img.Width; //1671
int x1 = e.MarginBounds.Left;//100
int y1 = e.MarginBounds.Top;//100
int w = e.MarginBounds.Width;//650
int h = e.MarginBounds.Height;//900
e.Graphics.DrawImageUnscaled(img, x1, y1, w, h);
}
I need resize this image proportionally heigth 411px.
how do this?
[HttpPost]
public WrappedJsonResult UploadImage(HttpPostedFileWrapper imageFile, int id)
{
if (imageFile == null || imageFile.ContentLength == 0)
{
return new WrappedJsonResult
{
Data = new
{
IsValid = false,
Message = "No file was uploaded.",
ImagePath = string.Empty
}
};
}
var fileName = String.Format("{0}_{1}.jpg", id, Guid.NewGuid().ToString());
var imagePath = Path.Combine(Server.MapPath(Url.Content("~/Content/UploadPhoto")), fileName);
imageFile.SaveAs(imagePath);
}
public ActionResult UploadImage(HttpPostedFileBase imageFile, int id)
{
if (imageFile == null || imageFile.ContentLength == 0)
{
return new WrappedJsonResult
{
Data = new
{
IsValid = false,
Message = "No file was uploaded.",
ImagePath = string.Empty
}
};
}
var fileName = String.Format("{0}_{1}.jpg", id, Guid.NewGuid().ToString());
var imagePath = Path.Combine(Server.MapPath(Url.Content("~/Content/UploadPhoto")), fileName);
using (var input = new Bitmap(imageFile.InputStream))
{
int width;
int height;
if (input.Width > input.Height)
{
width = 411;
height = 411 * input.Height / input.Width;
}
else
{
height = 411;
width = 411 * input.Width / input.Height;
}
using (var thumb = new Bitmap(width, height))
using (var graphic = Graphics.FromImage(thumb))
{
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = SmoothingMode.AntiAlias;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphic.DrawImage(input, 0, 0, width, height);
using (var output = System.IO.File.Create(imagePath))
{
thumb.Save(output, ImageFormat.Jpeg);
}
}
}
...
}
By the way you will notice that I have replaced HttpPostedFileWrapper with HttpPostedFileBase in the action signature as this is the correct type to use.
I've got an image upload page that works just fine when I only upload the files.
I added a 'Create Thumbnail' function. It looks like the file system has a handle on the images when the thumbnail process starts.
I get the 'unspecified GDI+ error' only when the image is over about 250K. When the files are below 250K, thumbnails are created as expected.
What are my options? Is there an elegant solution here? I want something not hacky.
Also, I am using HttpFileCollection so we can upload multiple images at one time. I've tried to use .Dispose on the Thumbnail creation, but it fails before we get to this point.
public void Upload_Click(object Sender, EventArgs e)
{
string directory = Server.MapPath(#"~\images\");
HttpFileCollection hfc = Request.Files;
for (int i = 0; i < hfc.Count; i++)
{
HttpPostedFile hpf = hfc[i];
if (hpf.ContentLength > 0)
{
string fileName = hpf.FileName;
fileName = fileName.Replace(" ", "");
hpf.SaveAs(fileName);
createThumbnail(fileName);
}
}
}
private void createThumbnail(string filename)
{
Image image = Image.FromFile(filename);
Image thumb = image.GetThumbnailImage(100,100, () => false, IntPtr.Zero);
thumb.Save(filename);
image.Dispose();
thumb.Dispose();
}
Please let me know if this works any better:
public string ImageDirectory { get { return Server.MapPath(#"~\images\"); } }
public void OnUploadClick(object sender, EventArgs e)
{
var files = HttpContext.Request.Files.AllKeys.AsEnumerable()
.Select(k =>HttpContext.Request.Files[k]);
foreach(var file in files)
{
if(file.ContentLength <= 0)
continue;
string savePath = GetFullSavePath(file);
var dimensions = new Size(100, 100);
CreateThumbnail(file,savePath,dimensions);
}
}
private void CreateThumbnail(HttpPostedFile file,string savePath, Size dimensions)
{
using (var image = Image.FromStream(file.InputStream))
{
using (var thumb = image.GetThumbnailImage(dimensions.Width, dimensions.Height, () => false, IntPtr.Zero))
{
thumb.Save(savePath);
}
}
}
private string GetFullSavePath(HttpPostedFile file)
{
string fileName = System.IO.Path.GetFileName(file.FileName).Replace(" ", "");
string savePath = System.IO.Path.Combine(this.ImageDirectory, fileName);
return savePath;
}
Edit -
The foreach should have followed more to this pattern:
var files = HttpContext.Request.Files.AllKeys.AsEnumerable()
.Select(k =>HttpContext.Request.Files[k]);
foreach(var file in files)
{
}
You can try this code to create your thumbnails.
MemoryStream ms = new MemoryStream(File.ReadAllBytes(path));
Bitmap originalBMP = new Bitmap(ms);
int maxWidth = 200;
int maxHeight = 200;
// Calculate the new image dimensions
int origWidth = originalBMP.Width;
int origHeight = originalBMP.Height;
double sngRatio = Convert.ToDouble(origWidth) / Convert.ToDouble(origHeight);
// New dimensions
int newWidth = 0;
int newHeight = 0;
try
{
// max 200 by 200
if ((origWidth <= maxWidth && origHeight <= maxHeight) || origWidth <= maxWidth)
{
newWidth = origWidth;
newHeight = origHeight;
}
else
{
// Width longer (shrink width)
newWidth = 200;
newHeight = Convert.ToInt32(Convert.ToDouble(newWidth) / sngRatio);
}
// Create a new bitmap which will hold the previous resized bitmap
Bitmap newBMP = new Bitmap(originalBMP, newWidth, newHeight);
// Create a graphic based on the new bitmap
Graphics oGraphics = Graphics.FromImage(newBMP);
// Set the properties for the new graphic file
oGraphics.SmoothingMode = SmoothingMode.AntiAlias;
oGraphics.InterpolationMode = InterpolationMode.High;
// Draw the new graphic based on the resized bitmap
oGraphics.CompositingQuality = CompositingQuality.HighSpeed;
oGraphics.DrawImage(originalBMP, 0, 0, newWidth, newHeight);
// Save the new graphic file to the server
EncoderParameters p = new EncoderParameters(1);
p.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, 70); // Percent Compression
MemoryStream savedBmp = new MemoryStream();
newBMP.Save(savedBmp, ImageCodecInfo.GetImageEncoders()[1], p);
// Once finished with the bitmap objects, we deallocate them.
originalBMP.Dispose();
newBMP.Dispose();
oGraphics.Dispose();
savedBmp.Dispose();
Certainly a bit more work but it does give you greater control.
Hey everyone,
So, I'm currently trying the implement the APNG Specification, but am having some trouble with the frame rendering. My function is
private void UpdateUI()
{
foreach (PictureBox pb in pics)
{
APNGBox box = (APNGBox)pb.Tag;
APNGLib.APNG png = box.png;
if (box.buffer == null)
{
box.buffer = new Bitmap((int)png.Width, (int)png.Height);
}
APNGLib.Frame f = png.GetFrame(box.frameNum);
using (Graphics g = Graphics.FromImage(box.buffer))
{
switch (f.DisposeOp)
{
case APNGLib.Frame.DisposeOperation.NONE:
break;
case APNGLib.Frame.DisposeOperation.BACKGROUND:
g.Clear(Color.Transparent);
break;
case APNGLib.Frame.DisposeOperation.PREVIOUS:
if (box.prevBuffer != null)
{
g.DrawImage(box.prevBuffer, Point.Empty);
}
else
{
g.Clear(Color.Transparent);
}
break;
default:
break;
}
Bitmap read = png.ToBitmap(box.frameNum++);
switch (f.BlendOp)
{
case APNGLib.Frame.BlendOperation.OVER:
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
break;
case APNGLib.Frame.BlendOperation.SOURCE:
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
break;
default:
break;
}
g.DrawImage(read, new Point((int)f.XOffset, (int)f.YOffset));
}
box.prevBuffer = box.buffer;
pb.Image = box.buffer;
if (box.frameNum >= box.png.FrameCount)
{
box.frameNum = 0;
box.buffer = null;
box.prevBuffer = null;
}
}
}
The APNGBox is
internal class APNGBox
{
public int frameNum = 0;
public APNGLib.APNG png;
public Bitmap buffer;
public Bitmap prevBuffer;
}
I think that this is mostly correct, as I've run it against all the images in the APNG Gallery. However, some of them are rendering incorrectly (This one has artifacting issues, and this one does not retain the red bar on the left consistently). Please note, you'll have the view the page in Firefox 3 or higher to view the animations.
I'm lead to believe the issue has to do with how I handle the DISPOSE_OP_PREVIOUS, but I can't figure out what I'm doing wrong. Can anyone suggest what I might be missing?
So, I was able to figure it out in the end, and will post here in case anyone in the future comes across a similar problem. Turns out the issue was largely threefold:
One should not change the 'previous buffer' if the frame type is 'PREVIOUS'
One should use the previous frame's dispose_op, not the dispose_op of the current frame, to determine how it disposes
One should be sure to dispose only the old frame's region, not the whole frame
The new code is:
internal class APNGBox
{
public int frameNum { get; set; }
public APNGLib.APNG apng { get; set; }
public Bitmap buffer { get; set; }
public Bitmap prevBuffer { get; set; }
public APNGBox(APNGLib.APNG png)
{
frameNum = 0;
apng = png;
buffer = apng.ToBitmap(0);
prevBuffer = null;
}
}
private void UpdateUI()
{
foreach (PictureBox pb in pics)
{
APNGBox box = (APNGBox)pb.Tag;
APNGLib.APNG png = box.apng;
if (!png.IsAnimated)
{
if (pb.Image == null)
{
pb.Image = png.ToBitmap();
}
}
else
{
if (box.frameNum != png.FrameCount - 1)
{
Bitmap prev = box.prevBuffer == null ? null : new Bitmap(box.prevBuffer);
APNGLib.Frame f1 = png.GetFrame(box.frameNum);
if (f1.DisposeOp != APNGLib.Frame.DisposeOperation.PREVIOUS)
{
box.prevBuffer = new Bitmap(box.buffer);
}
DisposeBuffer(box.buffer, new Rectangle((int)f1.XOffset, (int)f1.YOffset, (int)f1.Width, (int)f1.Height), f1.DisposeOp, prev);
box.frameNum++;
APNGLib.Frame f2 = png.GetFrame(box.frameNum);
RenderNextFrame(box.buffer, new Point((int)f2.XOffset, (int)f2.YOffset), png.ToBitmap(box.frameNum), f2.BlendOp);
}
else
{
box.frameNum = 0;
box.prevBuffer = null;
ClearFrame(box.buffer);
RenderNextFrame(box.buffer, Point.Empty, png.ToBitmap(box.frameNum), APNGLib.Frame.BlendOperation.SOURCE);
}
pb.Invalidate();
}
}
}
private void DisposeBuffer(Bitmap buffer, Rectangle region, APNGLib.Frame.DisposeOperation dispose, Bitmap prevBuffer)
{
using (Graphics g = Graphics.FromImage(buffer))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
Brush b = new SolidBrush(Color.Transparent);
switch (dispose)
{
case APNGLib.Frame.DisposeOperation.NONE:
break;
case APNGLib.Frame.DisposeOperation.BACKGROUND:
g.FillRectangle(b, region);
break;
case APNGLib.Frame.DisposeOperation.PREVIOUS:
if(prevBuffer != null)
{
g.FillRectangle(b, region);
g.DrawImage(prevBuffer, region, region, GraphicsUnit.Pixel);
}
break;
default:
break;
}
}
}
private void RenderNextFrame(Bitmap buffer, Point point, Bitmap nextFrame, APNGLib.Frame.BlendOperation blend)
{
using(Graphics g = Graphics.FromImage(buffer))
{
switch(blend)
{
case APNGLib.Frame.BlendOperation.OVER:
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
break;
case APNGLib.Frame.BlendOperation.SOURCE:
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
break;
default:
break;
}
g.DrawImage(nextFrame, point);
}
}
private void ClearFrame(Bitmap buffer)
{
using(Graphics g = Graphics.FromImage(buffer))
{
g.Clear(Color.Transparent);
}
}