I have a few (8-10) large texture atlases (around 8192x8192 saved as BC1 DDS) but they are rather small disk space wise, only 32MiB.
The problem is, that when I load those files using WIC, they lose their block compression and take up to 256MiB of RAM, which shouldn't happen according to the sources
Here is my C# SharpDX code which loads the file:
public Bitmap LoadBitmap(string path) {
Bitmap m;
using (BitmapDecoder d = new BitmapDecoder(imagingFactory, $"plugins/HDPatch/{path}", Guid.Empty, SharpDX.IO.NativeFileAccess.Read, DecodeOptions.CacheOnDemand)) {
DdsDecoder decoder = d.QueryInterface<DdsDecoder>();
decoder.GetFrame(0, 0, 0, out BitmapFrameDecode frame);
m = SharpDX.Direct2D1.Bitmap.FromWicBitmap(_target, frame);//_target.CreateBitmap(converter, new BitmapProperties());
decoder.Dispose();
frame.Dispose();
}
return m;
}
Do I need to change something, or should I use a different method of loading them than WIC?
I looked at the DdsFrameDecode.CopyBlocks (+ ID2D1Bitmap::CopyFromMemory) method but I do not know how that works.. (What exactly is my stride and where do I get that information from)
EDIT:
So the BitmapFrameDecode gives me a PixelFormat of 32bppPBGRA
If I query a DDSFrameDecode from that BitmapFrameDecode, I get a format of BC1_UNorm from the DdsFrameDecode. The DdsDecoder also reports a texture with DdsAlphaModePremultiplied, and a DxgiFormat of BC1_UNorm.
But when I force the pixel format of BC1_UNorm in the BitmapProperties I get the D2D DEBUG ERROR: "The pixel format passed to this API is not compatible with the pixel format of the IWICBitmapSource"
EDIT2:
I figured out how to use CopyBlocks
SharpDX.DataStream stream = new
SharpDX.DataStream(test2.FormatInfo.BytesPerBlock * test2.SizeInBlocks.Width * test2.SizeInBlocks.Height, true, true);
test2.CopyBlocks(null, test2.FormatInfo.BytesPerBlock * test2.SizeInBlocks.Width, stream);
m = new Bitmap(_target, new SharpDX.Size2(decoder.Parameters.Width, decoder.Parameters.Height), stream, test2.FormatInfo.BytesPerBlock * test2.FormatInfo.BlockWidth, new BitmapProperties(new PixelFormat(SharpDX.DXGI.Format.BC1_UNorm, AlphaMode.Premultiplied)));
But the problem still exists. It can successfully create the Bitmap but it still consumes to much memory (250MiB+ instead of the actual block compressed size of 32MiB)
Does it maybe have to do with the render target of?
I use a software B8G8R8A8_UNorm DeviceContext render target
EDIT3:
Example project: https://drive.google.com/file/d/1hCgQnEV9xcTevgdswAiPJNREk1oDgqDC/view?usp=sharing
Related
I am currently trying to create thumbnails the moment I am decoding them to avoid loading the entire image into memory and then scaling it. Secondly I want to get rid from my other thumbnail code which is using ShellObjects and what the OS file explorer cached as thumbnails. The problem with the later is that its depending on if there is anything cached.
The following code is my attempt to create an image the moment its decoded which fails with a Format Unknown error. I am pretty close to have the solution so I am coming here because I have not found an answer. Every "solution" I found loaded the entire file, creating two images, scaling the original which creates more overhead than what I believe is needed to accomplish this. Pretty resource intensive for an image manager with a thousand image files being loaded asynchronously.)
public static async Task<Texture2D> GetThumbnail(string filePath)
{
// Decode the image directly in the given DecodePixelHeight (or width), maintaining aspect ratio.
var thumbnail = new BitmapImage();
thumbnail.BeginInit();
thumbnail.UriSource = new Uri(filePath, UriKind.Absolute);
thumbnail.DecodePixelHeight = 144; //Fit to this height.
thumbnail.EndInit();
// Here I am trying reset a format. I am aiming to not need this step.
// Format the bitmap image into a known format.
var formatted = new FormatConvertedBitmap();
formatted.BeginInit();
formatted.Source = thumbnail;
formatted.DestinationFormat = System.Windows.Media.PixelFormats.Default;
formatted.EndInit();
using var stream = new MemoryStream();
var bytesPerPixel = (formatted.DestinationFormat.BitsPerPixel + 7) / 8;
var stride = 4 * ((formatted.PixelWidth * bytesPerPixel + 3) / 4);
var buffer = new byte[formatted.PixelHeight * stride];
formatted.CopyPixels(buffer, stride, 0);
await stream.WriteAsync(buffer, 0, buffer.Length);
return Texture2D.FromStream(___.GraphicsDevice, stream);
}
I load a multiframe TIFF from a Stream in my C# application, and then save it using the Image.Save method. However, this only saves the TIFF with the first frame - how can I get it to save a multiframe tiff?
Since you don't provide any detailed information... just some general tips:
Multi-Frame TIFF are very complex files - for example every frame can have a different encoding... a single Bitmap/Image can't hold all frames with all relevant information (like encoding and similar) of such a file, only one at a time.
For loading you need to set parameter which tells the class which frame to load, otherwise it just loads the first... for some code see here.
Similar problems arise when saving multi-frame TIFFs - here you need to work with EncoderParameters and use SaveAdd etc. - for some working code see here.
Since the link to code provided by #Yahia is broken I have decided to post the code I ended up using.
In my case, the multi-frame TIFF already exists and all I need to do is to load the image, rotate by EXIF (if necessary) and save. I won't post the EXIF rotation code here, since it does not relate to this question.
using (Image img = System.Drawing.Image.FromStream(sourceStream))
{
using (FileStream fileStream = System.IO.File.Create(filePath))
{
int pages = img.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page);
if (pages == 1)
{
img.Save(fileStream, img.RawFormat); // if there is just one page, just save the file
}
else
{
var encoder = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders().First(x => x.MimeType == fileInfo.MediaType);
var encoderParams = new System.Drawing.Imaging.EncoderParameters(1);
encoderParams.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(System.Drawing.Imaging.EncoderValue.MultiFrame));
img.Save(fileStream, encoder, encoderParams); // save the first image with MultiFrame parameter
for (int f = 1; f < pages; f++)
{
img.SelectActiveFrame(FrameDimension.Page, f); // select active page (System.Drawing.Image.FromStream loads the first one by default)
encoderParams.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(System.Drawing.Imaging.EncoderValue.FrameDimensionPage));
img.SaveAdd(img, encoderParams); // save add with FrameDimensionPage parameter
}
}
}
}
sourceStream is a System.IO.MemoryStream which holds the byte array of the file content
filePath is absolute path to cache directory (something like 'C:/Cache/multiframe.tiff')
fileInfo is a model holding the actual byte array, fileName, mediaType and other data
I have the Image of a PictureBox pointing to a certain file "A". At execution time I want to change the Image of the PictureBox to a different one "B" but I get the following error:
"A first chance exception of type 'System.IO.IOException' occurred in mscorlib.dll
Additional information: The process cannot access the file "A" because it is being used by another process."
I'm setting the Image as follows:
pbAvatar.Image = new Bitmap(filePath);
How can I unlock the first file?
Here is my approach to opening an image without locking the file...
public static Image FromFile(string path)
{
var bytes = File.ReadAllBytes(path);
var ms = new MemoryStream(bytes);
var img = Image.FromStream(ms);
return img;
}
UPDATE: I did some perf tests to see which method was the fastest. I compared it to #net_progs "copy from bitmap" answer (which seems to be the closest to correct, though does have some issues). I loaded the image 10000 times for each method and calculated the average time per image. Here are the results:
Loading from bytes: ~0.26 ms per image.
Copying from bitmap: ~0.50 ms per image.
The results seem to make sense since you have to create the image twice using the copy from bitmap method.
UPDATE:
if you need a BitMap you can do:
return (Bitmap)Image.FromStream(ms);
This is a common locking question widely discussed over the web.
The suggested trick with stream will not work, actually it works initially, but causes problems later. For example, it will load the image and the file will remain unlocked, but if you try to save the loaded image via Save() method, it will throw a generic GDI+ exception.
Next, the way with per pixel replication doesn't seem to be solid, at least it is noisy.
What I found working is described here: http://www.eggheadcafe.com/microsoft/Csharp/35017279/imagefromfile--locks-file.aspx
This is how the image should be loaded:
Image img;
using (var bmpTemp = new Bitmap("image_file_path"))
{
img = new Bitmap(bmpTemp);
}
I was looking for a solution to this problem and this method works fine for me so far, so I decided to describe it, since I found that many people advise the incorrect stream approach here and over the web.
Using a filestream will unlock the file once it has been read from and disposed:
using (var fs = new System.IO.FileStream("c:\\path to file.bmp", System.IO.FileMode.Open))
{
var bmp = new Bitmap(fs);
pct.Image = (Bitmap) bmp.Clone();
}
Edit: Updated to allow the original bitmap to be disposed, and allow the FileStream to be closed.
THIS ANSWER IS NOT SAFE - See comments, and see discussion in net_prog's answer. The Edit to use Clone does not make it any safer - Clone clones all fields, including the filestream reference, which in certain circumstances will cause a problem.
You can't dispose / close a stream while a bitmap object is still using it. (Whether the bitmap object will need access to it again is only deterministic if you know what type of file you are working with and exactly what operations you will be performing. -- for example for SOME .gif format images, the stream is closed before the constructor returns.)
Clone creates an "exact copy" of the bitmap (per documentation; ILSpy shows it calling native methods, so it's too much to track down right now) likely, it copies that Stream data as well -- or else it wouldn't be an exact copy.
Your best bet is creating a pixel-perfect replica of the image -- though YMMV (with certain types of images there may be more than one frame, or you may have to copy palette data as well.) But for most images, this works:
static Bitmap LoadImage(Stream stream)
{
Bitmap retval = null;
using (Bitmap b = new Bitmap(stream))
{
retval = new Bitmap(b.Width, b.Height, b.PixelFormat);
using (Graphics g = Graphics.FromImage(retval))
{
g.DrawImage(b, Point.Empty);
g.Flush();
}
}
return retval;
}
And then you can invoke it like such:
using (Stream s = ...)
{
Bitmap x = LoadImage(s);
}
As far as I know, this is 100% safe, since the resulting image is 100% created in memory, without any linked resources, and with no open streams left behind in memory. It acts like any other Bitmap that's created from a constructor that doesn't specify any input sources, and unlike some of the other answers here, it preserves the original pixel format, meaning it can be used on indexed formats.
Based on this answer, but with extra fixes and without external library import.
/// <summary>
/// Clones an image object to free it from any backing resources.
/// Code taken from http://stackoverflow.com/a/3661892/ with some extra fixes.
/// </summary>
/// <param name="sourceImage">The image to clone</param>
/// <returns>The cloned image</returns>
public static Bitmap CloneImage(Bitmap sourceImage)
{
Rectangle rect = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
Bitmap targetImage = new Bitmap(rect.Width, rect.Height, sourceImage.PixelFormat);
targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
BitmapData sourceData = sourceImage.LockBits(rect, ImageLockMode.ReadOnly, sourceImage.PixelFormat);
BitmapData targetData = targetImage.LockBits(rect, ImageLockMode.WriteOnly, targetImage.PixelFormat);
Int32 actualDataWidth = ((Image.GetPixelFormatSize(sourceImage.PixelFormat) * rect.Width) + 7) / 8;
Int32 h = sourceImage.Height;
Int32 origStride = sourceData.Stride;
Boolean isFlipped = origStride < 0;
origStride = Math.Abs(origStride); // Fix for negative stride in BMP format.
Int32 targetStride = targetData.Stride;
Byte[] imageData = new Byte[actualDataWidth];
IntPtr sourcePos = sourceData.Scan0;
IntPtr destPos = targetData.Scan0;
// Copy line by line, skipping by stride but copying actual data width
for (Int32 y = 0; y < h; y++)
{
Marshal.Copy(sourcePos, imageData, 0, actualDataWidth);
Marshal.Copy(imageData, 0, destPos, actualDataWidth);
sourcePos = new IntPtr(sourcePos.ToInt64() + origStride);
destPos = new IntPtr(destPos.ToInt64() + targetStride);
}
targetImage.UnlockBits(targetData);
sourceImage.UnlockBits(sourceData);
// Fix for negative stride on BMP format.
if (isFlipped)
targetImage.RotateFlip(RotateFlipType.Rotate180FlipX);
// For indexed images, restore the palette. This is not linking to a referenced
// object in the original image; the getter of Palette creates a new object when called.
if ((sourceImage.PixelFormat & PixelFormat.Indexed) != 0)
targetImage.Palette = sourceImage.Palette;
// Restore DPI settings
targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
return targetImage;
}
To call, simply use:
/// <summary>Loads an image without locking the underlying file.</summary>
/// <param name="path">Path of the image to load</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(String path)
{
using (Bitmap sourceImage = new Bitmap(path))
{
return CloneImage(sourceImage);
}
}
Or, from bytes:
/// <summary>Loads an image from bytes without leaving open a MemoryStream.</summary>
/// <param name="fileData">Byte array containing the image to load.</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(Byte[] fileData)
{
using (MemoryStream stream = new MemoryStream(fileData))
using (Bitmap sourceImage = new Bitmap(stream)) {
{
return CloneImage(sourceImage);
}
}
Here's the technique I'm currently using, and seems to work best. It has the advantage of producing a Bitmap object with the same pixel format (24-bit or 32-bit) and resolution (72 dpi, 96 dpi, whatever) as the source file.
// ImageConverter object used to convert JPEG byte arrays into Image objects. This is static
// and only gets instantiated once.
private static readonly ImageConverter _imageConverter = new ImageConverter();
This can be used as often as needed, as follows:
Bitmap newBitmap = (Bitmap)_imageConverter.ConvertFrom(File.ReadAllBytes(fileName));
Edit:
Here's an update of the above technique: https://stackoverflow.com/a/16576471/253938
(The accepted answer is wrong. When you try to LockBits(...) on the cloned bitmap eventually you will encounter GDI+ errors.)
I see only 3 ways to get out of this:
copy your file to a temporary file and open that the easy way new Bitmap(temp_filename)
open your file, read image, create a pixel-size-pixelformat copy (don't Clone()) and dispose the first bitmap
(accept the locked-file-feature)
I suggest to use PixelMap (available on NuGet)
or Github
Very easy to use and much faster than standard Bitmap from .NET
PixelMap pixelMap = new PixelMap(bild);
pictureBox1.Image = pixelMap.GetBitmap();
Read it into the stream, create bitmap, close the stream.
I'm working with the Kinect 2.0 and in particular with the color stream. The color stream arrives with a whooping 1920×1080 resolution, which is great! Except, I intend to capture the image bytes and write them to disk. So, the most viable solution for me is to compress each image frame and then store the compressed image rather than the raw high resolution image.
I have a solution but I feel its a bit of a "round the houses" way of doing this.
Basically, I get the raw pixels, write them to a WriteableBitmap and then compress the ImageSource using the following two methods:
1) Write to WriteableBitmap:
this.colorBitmap.WritePixels(
new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight),
this.colorPixels,
this.colorBitmap.PixelWidth * (int)this.bytesPerPixel,
0);
2) Compress this to jpeg:
public static byte[] CompressToBytes(ImageSource src, int compressionrate)
{
var enc = new JpegBitmapEncoder();
enc.QualityLevel = compressionrate;
var bf = BitmapFrame.Create((BitmapSource)src);
enc.Frames.Add(bf);
using (var ms = new MemoryStream())
{
enc.Save(ms);
return ms.ToArray();
}
}
Which works fine, as I said, but I think it would be much better if I could get the raw pixels from the Kinect and then directly compress byte array rather than writing to a WriteableBitmap and then compressing that. Just seems like an extra step.
BTW, this is the code I use to grab the bytes from the color frame:
using (ColorFrame colorFrame = e.FrameReference.AcquireFrame())
{
if (colorFrame != null)
{
FrameDescription colorFrameDescription = colorFrame.FrameDescription;
if ((colorFrameDescription.Width == this.colorBitmap.PixelWidth) && (colorFrameDescription.Height == this.colorBitmap.PixelHeight))
{
if (colorFrame.RawColorImageFormat == ColorImageFormat.Bgra)
{
colorFrame.CopyRawFrameDataToArray(this.colorPixels);
}
else
{
colorFrame.CopyConvertedFrameDataToArray(this.colorPixels, ColorImageFormat.Bgra);
}
}
}
}
Baring in mind that when working with the color stream at all the number of frames per second you are able to capture drops, (from say 30-32 to 26-29 roughly), I want to use the most efficient way.
One thing I've noticed that when searching for compression solutions in c#, is that a good percentage of articles are aimed at storing a single image to disk. Whereas I need this compression to be carried out in memory, as I am writing multiple images to disk (using a binary writer).
I have a form (using MVC2) which has an image-upload script, but the rules for the final image stored on the server are pretty strict. I can force the file to the dimensions I want but it always ends up exceeding the file-size required... so I can allow a sub-200k image but once my code has processed it ends up slightly bigger.
These are the rules I have to adhere to:
Photographs should be in colour
The permitted image types for the
photograph are .JPG or .GIF
The maximum size of the image is 200kb
The dimensions of the photograph on the badge will be 274 pixels
(wide) x 354 pixels (high) # 200dpi (depth of pixels per inch)
This is what I have currently:
[HttpPost]
public ActionResult ImageUpload(HttpPostedFileBase fileBase)
{
ImageService imageService = new ImageService();
if (fileBase != null && fileBase.ContentLength > 0 && fileBase.ContentLength < 204800 && fileBase.ContentType.Contains("image/"))
{
string profileUploadPath = "~/Resources/images";
Path.GetExtension(fileBase.ContentType);
var newGuid = Guid.NewGuid();
var extension = Path.GetExtension(fileBase.FileName);
if (extension.ToLower() != ".jpg" && extension.ToLower() != ".gif") // only allow these types
{
return View("WrongFileType", extension);
}
EncoderParameters encodingParameters = new EncoderParameters(1);
encodingParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 70L); // Set the JPG Quality percentage
ImageCodecInfo jpgEncoder = imageService.GetEncoderInfo("image/jpeg");
var uploadedimage = Image.FromStream(fileBase.InputStream, true, true);
Bitmap originalImage = new Bitmap(uploadedimage);
Bitmap newImage = new Bitmap(originalImage, 274, 354);
Graphics g = Graphics.FromImage(newImage);
g.InterpolationMode = InterpolationMode.HighQualityBilinear;
g.DrawImage(originalImage, 0, 0, newImage.Width, newImage.Height);
var streamLarge = new MemoryStream();
newImage.Save(streamLarge, jpgEncoder, encodingParameters);
var fileExtension = Path.GetExtension(extension);
var ImageName = newGuid + fileExtension;
newImage.Save(Server.MapPath(profileUploadPath) + ImageName);
//newImage.WriteAllBytes(Server.MapPath(profileUploadPath) + ImageName, streamLarge.ToArray());
originalImage.Dispose();
newImage.Dispose();
streamLarge.Dispose();
return View("Success");
}
return View("InvalidImage");
}
Just to add:
The images are going off to print on a card so the DPI is important. But I realise that 200k is not a lot for a printed image.. none of these are my business rules! As it stands with this code an image uploaded that is pretty much 200k, ends up costing 238k(ish)
It's very difficult to calculate the size of a jpeg in advance. Having said that, you don't need to compress it much.
Let's just look at some metrics:
274 * 354 = 96996 pixels. If you have 8 bits per pixel and 3 colour
channels (i.e. 24bit colour) then you have:
274* 354 * 8 * 3 = 2,327,904 bits = 290988 bytes = 284.17 kb.
200 / 284.17 ~ 0.70.
You only need to reduce it to 70% of its original size.
Sadly, it's at this point we get to the limit of my knowledge in this area! But I reckon that by saving as a jpeg it will be in the right size range anyway, even if saving at the highest quality setting.
I would guess at setting the quality to 70 and see what happens.
EDIT: DPI settings
Apparently you only need to change the EXIF data. See this answer: https://stackoverflow.com/a/4427411/234415
You should experiment with the JPEG quality setting. You currently have it set to 90, 80 might be sufficient and will result in a smaller file.
I see some problems with the code:
You are using GetThumbnailImage to create a thumbnail, but that is not intended for such large thumbnails. It works up to about 120x120 pixels. If the image has an embedded thumbnail, that will be used instead of scaling down the full image, so you will be scaling up a smaller image, with obvious quality problems.
You are saving the thumbnail to a memory stream, which you then just throw away.
You are saving the thumbnail to file without specifying the encoder, which means that it will either be saved as a low compressed JPEG image or a PNG image, that's why you get a larger file size.
You never dispose the uploadedImage object.
Note: The resolution (PPI/DPI) has no relevance when you display images on the web.