I am trying to write a method that takes image data in base64 string and saves it into a binary file while preserving transparency (for example in case of PNGs).
My other requirement is that this needs to be done in C# in PCL (Portable Class Library).
I know that you can use Image or WriteableBitmap to solve this issue but such classes are not available in PCL.
I have the following method that does the job of taking the base64 data and saving it to a file:
public static async Task Base64ToBinaryImageFile(IFile file, string base64Content)
{
var bytes = Convert.FromBase64String(base64Content);
using (var stream = await file.OpenAsync(FileAccess.ReadAndWrite))
{
stream.Seek(0, SeekOrigin.Begin);
using (var writer = new BinaryWriter(stream))
{
writer.Write(content);
writer.Flush();
}
}
}
It works fine except that:
I lose the transparency data (so transparent pixels show up as black).
The file that is created using this method has a larger size (in bytes) than the original file.
Any idea on what's the cause and how to fix these issues?
Update: Here is the JavaScript code that sends the base64 data to C#:
function onPaste(event) {
var $event = event.data.$;
var clipboardData = $event.clipboardData;
var found = false;
var imageType = /^image/;
if (!clipboardData) {
return false;
}
return Array.prototype.forEach.call(clipboardData.types, function (type, i) {
if (found) {
return false;
}
if (type.match(imageType) || clipboardData.items[i].type.match(imageType)) {
readImageAsBase64(clipboardData.items[i]);
return found = true;
}
return false;
});
}
function readImageAsBase64(item) {
if (!item || typeof item.getAsFile !== "function") {
return;
}
var file = item.getAsFile();
var reader = new FileReader();
reader.onload = function (evt) {
window.external.notify("pasteImageBase64/" + evt.target.result);
};
reader.readAsDataURL(file);
}
I foresee a few possible issues:
Your issue could reside in the base64Content provided by the caller. It's possible that the conversion to base64Content that is provided as input to your method is reading the PNG image with an incorrect file format.
Related to #1, it's possible that someone calling the method took a .JPG or .BMP image file, naively renamed it to .PNG extension and called your method assuming that they were sending a PNG image, when in fact they were not.
You may be opening the .PNG image in testing with an image viewer/editor that does not support transparency or handle it well (IE mspaint.exe)
Related
I am having the issue of "Canvas Drawing too large bitmaps". After a quick search, I found the following thread, which promptly helped me know what is the issue.
The solution is to put the image in drawable-xxhdpi/ instead of simply drawable/. And here lies the issue: the image is not static, it is imported when I need it. As such, I do not chose where the image ends up stored. It store itself in drawable. Is there 1) A solution to chose which folder to use, or 2) a way to tell it not get the image if it's too heavy?
var file = new SmbFile(path, auth);
try
{
if (file.Exists())
{
// Get readable stream.
var readStream = file.GetInputStream();
//Create reading buffer.
MemoryStream memStream = new MemoryStream();
//Get bytes.
((Stream)readStream).CopyTo(memStream);
var stream1 = new MemoryStream(memStream.ToArray());
if (stream1.Length < 120188100)
{
//Save image
ProductImage = ImageSource.FromStream(() => stream1);
//Dispose readable stream.
readStream.Dispose();
InfoColSpan = 1;
}
else
{
Common.AlertError("Image trop lourde pour l'affichage");
}
}
}
I am trying to print an RDL report with terms and conditions PDF. The problem is that the report itself is a Queue of images, whereas the T&C's are in PDF format. So whenever I do an "Enqueue", adding to the streams, it's looking at that PDF like one big image, as opposed to two pages. This causes a GDI+ generics error. Is there anyway for me to convert the PDF into the proper image format so that I can combine these documents? Here's the code I have so far:
internal static void DoPrintInvoice(int orderID, SalesOrderBLL.DocumentType doctype, string printer, int copies, List<string> lines)
{
using (var context = rempscoDataContext.CreateReadOnlyContext())
using (MiniProfiler.Current.Step("DoPrintInvoice()"))
{
//Customer Opt-Out
// Generate Report
using (var report = GetSalesOrderReport(orderID, _DocumentTypeDescriptions[doctype], doctype != DocumentType.InvoiceLetterhead, lines))
{
// returns queue of streams.
var streams = PrintingBLL.RenderStreams(report, landscape: false);
// returns byte array
var TermsAndConditions = GetTermsAndConditions();
//convert byte array to memory stream.
var TCStream = new MemoryStream(TermsAndConditions);
//conditional to add T&C's to stream.
if (doctype == DocumentType.OrderAcknowledgement)
{
streams.Enqueue(TCStream);
}
ParallelInvoke(
() => SaveSalesOrderPDF(orderID, doctype, report),
() => PrintingBLL.PrintStreams(streams, string.Format("Sales Order ({0})", report.DisplayName), printer, copies, false)
);
}
}
}
I've tried to convert the terms and conditions into an image, and back to a byte array but it gives me the same GDI generic issue. Any help would be greatly appreciated!
Have you looked at PDFSharp? I have had good luck working with it in the past for rendering PDFs.
www.pdfsharp.com
I'm trying to get a byte array from the InkCanvas control, but the method i've come up with so far seems a bit long winded.
Currently I use the following:
StorageFolder folder = await Windows.Storage.ApplicationData.Current.LocalFolder.GetFolderAsync("Temp");
StorageFile file = await folder.CreateFileAsync(GenerateString(5)+".zzx", Windows.Storage.CreationCollisionOption.GenerateUniqueName);
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await SignatureCanvas.InkPresenter.StrokeContainer.SaveAsync(stream);
var array = await IRandomAccessStreamToByteArray(stream);
}
The custom stream reader is as follows.
private async Task<byte[]> IRandomAccessStreamToByteArray(IRandomAccessStream stream)
{
var reader = new DataReader(stream.GetInputStreamAt(0));
var bytes = new byte[stream.Size];
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(bytes);
return bytes;
}
This works, and gives me the byte array that i need, but also leaves me with unwanted images. Was having some access issues due to files still being written when another call wanted to replace the file so decided to go down the multiple images route. Is there a way to skip the image file entirely? It's not too much of an issue to clear out a temp folder, but if it can be avoided that would be preferable.
I had read somewhere already that InkCanvas doesn't support direct to array dumps, so any suggestions would be appreciated!
If you want to save your InkCanvas strokes to bytes Array without create the file, you should be able to use the Win2D.uwp.
To install Win2D.uwp, run the "Install-Package Win2D.uwp" command in the Package Manager Console.
There is a CanvasDrawingSession.DrawInk method to draw a collection of ink strokes. That we should be able to use CanvasBitmap.GetPixelBytes method to get an array of raw byte data for the entire bitmap.
For example:
private byte[] ConvertInkCanvasToByteArray()
{
var canvasStrokes = SignatureCanvas.InkPresenter.StrokeContainer.GetStrokes();
if (canvasStrokes.Count > 0)
{
var width = (int)SignatureCanvas.ActualWidth;
var height = (int)SignatureCanvas.ActualHeight;
var device = CanvasDevice.GetSharedDevice();
device.DeviceLost += DeviceOnDeviceLost;
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
using (var ds = renderTarget.CreateDrawingSession())
{
ds.Clear(Windows.UI.Colors.White);
ds.DrawInk(SignatureCanvas.InkPresenter.StrokeContainer.GetStrokes());
}
return renderTarget.GetPixelBytes();
}
else
{
return null;
}
}
private void DeviceOnDeviceLost(CanvasDevice sender, object args)
{
Debug.WriteLine("DeviceOnDeviceLost");
}
Also if we want to convert the bytes array to image, we should be able to use following code:
WriteableBitmap bitmap = new WriteableBitmap((int)SignatureCanvas.ActualWidth, (int)SignatureCanvas.ActualHeight);
await bitmap.PixelBuffer.AsStream().WriteAsync(mybytes, 0, mybytes.Length);
I have spent the last 10-12 hours trying to figure out how to correctly make a downloaded web image smaller in size and pixels in C# in a Windows Store app under development.
Whatever I do, I keep getting artifacts on the final images such as a "half picture", gray/same-colored areas and likewise. Like if the stream has not been flushed correctly although I believe to have done so (not done so in the code below since it works without it...)
This is my approach for retrieving the image - this part works, but is included here to make sure all info is here (see code below):
Get URL of image
Use HttpWebRequest to get response
Create stream to get response stream
Create empty StorageFile and open for writing
Copy the response stream to the storage file.
Close everything
From there, I need to do the following:
Determine the size (e.g. using BitmapDecoder)
If the width of the image is above a certain amount (e.g. 700 px), it must be resized.
No matter what, the files are always too big and need to be compressed further
The image need to be saved as a jpg with image quality set to a medium/semi-high setting
I have tried many things including messing pretty much around with BitmapEncoder/BitmapDecoder, but no matter what I am still getting half-processed images.
Can somebody please help me find the correct way to compress and resize images?
My code in the current state:
using (var response = await HttpWebRequest.CreateHttp(internetUri).GetResponseAsync())
{
using (var stream = response.GetResponseStream())
{
var imageFolder = await localFolder.CreateFolderAsync(
CachedImagesFolderEndFolderPath, CreationCollisionOption.OpenIfExists);
string fileName = string.Format("{0}.jpg",
Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
var file = await imageFolder.CreateFileAsync(fileName,
CreationCollisionOption.ReplaceExisting);
using (var filestream = await file.OpenStreamForWriteAsync())
{
await stream.CopyToAsync(filestream);
}
}
}
The following solution was provided by StefanDK in this edit:
It seems that the problem with my former solution was that I did not properly close the streams and that I did not have the correct settings.
Basically the solution incorporates elements from these articles:
How to resize Image in C# WinRT/winmd?
https://stackoverflow.com/questions/15481126/windows-store-app-resize-bitmapimage-c-sharp
http://msdn.microsoft.com/en-us/library/windows/apps/jj709942.aspx
http://msdn.microsoft.com/en-us/library/windows/apps/hh465076.aspx
From the main part of the code I make these calls for each image that needs downloading, resizing and compressing:
Main code
Note that I am well aware of the "not best practice" in assigning a string value and then setting it again. This is prototype code that has not been fine-tuned yet.
var img = await ArticleStorage.GetLocalImageAsync(src);
img = await ArticleStorage.ResizeAndCompressLocalImage(img);
Source code of the methods in ArticleStorage
public const string CachedImagesFolderFullPath = "ms-appdata:///local/cache/";
public const string CachedImagesFolderEndFolderPath = "cache";
public const string OfflinePhotoImgPath = "ms-appx:///Assets/OfflinePhoto.png";
public const int MaximumColumnWidth = 700;
public static async Task<string> GetLocalImageAsync(string internetUri)
{
if (string.IsNullOrEmpty(internetUri))
{
return null;
}
// Show default image if local folder does not exist
var localFolder = ApplicationData.Current.LocalFolder;
if (localFolder == null)
{
return OfflinePhotoImgPath;
}
// Default to offline photo
string src = OfflinePhotoImgPath;
try
{
using (var response = await HttpWebRequest.CreateHttp(internetUri)
.GetResponseAsync())
{
using (var stream = response.GetResponseStream())
{
// New random filename (e.g. x53fjtje.jpg)
string fileName = string.Format("{0}.jpg",
Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
var imageFolder = await localFolder.CreateFolderAsync(
CachedImagesFolderEndFolderPath,
CreationCollisionOption.OpenIfExists);
var file = await imageFolder.CreateFileAsync(fileName,
CreationCollisionOption.ReplaceExisting);
// Copy bytes from stream to local file
// without changing any file information
using (var filestream = await file.OpenStreamForWriteAsync())
{
await stream.CopyToAsync(filestream);
// Send back the local path to the image
// (including 'ms-appdata:///local/cache/')
return string.Format(CachedImagesFolderFullPath + "{0}",
fileName);
}
}
}
}
catch (Exception)
{
// Is implicitly handled with the setting
// of the initilized value of src
}
// If not succesfull, return the default offline image
return src;
}
public static async Task<string> ResizeAndCompressLocalImage(string imgSrc)
{
// Remove 'ms-appdata:///local/cache/' from the path ...
string sourcepathShort = imgSrc.Replace(
CachedImagesFolderFullPath,
string.Empty);
// Get the cached images folder
var folder = await ApplicationData.Current
.LocalFolder
.GetFolderAsync(
CachedImagesFolderEndFolderPath);
// Get a new random name (e.g. '555jkdhr5.jpg')
var targetPath = string.Format("{0}.jpg",
Path.GetFileNameWithoutExtension(
Path.GetRandomFileName()));
// Retrieve source and create target file
var sourceFile = await folder.GetFileAsync(sourcepathShort);
var targetFile = await folder.CreateFileAsync(targetPath);
using (var sourceFileStream = await sourceFile.OpenAsync(
Windows.Storage.FileAccessMode.Read))
{
using (var destFileStream = await targetFile.OpenAsync(
FileAccessMode.ReadWrite))
{
// Prepare decoding of the source image
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(
sourceFileStream);
// Find out if image needs resizing
double proportionWidth = (double)decoder.PixelWidth /
LayoutDimensions.MaximumColumnWidth;
double proportionImage = decoder.PixelHeight /
(double)decoder.PixelWidth;
// Get the new sizes of the image whether it is the same or should be resized
var newWidth = proportionWidth > 1 ?
(uint)(MaximumColumnWidth) :
decoder.PixelWidth;
var newHeight = proportionWidth > 1 ?
(uint)(MaximumColumnWidth * proportionImage) :
decoder.PixelHeight;
// Prepare set of properties for the bitmap
BitmapPropertySet propertySet = new BitmapPropertySet();
// Set ImageQuality
BitmapTypedValue qualityValue = new BitmapTypedValue(0.75,
PropertyType.Single);
propertySet.Add("ImageQuality", qualityValue);
//BitmapEncoder enc = await BitmapEncoder.CreateForTranscodingAsync(
destFileStream, decoder);
BitmapEncoder enc = await BitmapEncoder.CreateAsync(
BitmapEncoder.JpegEncoderId,
destFileStream, propertySet);
// Set the new dimensions
enc.BitmapTransform.ScaledHeight = newHeight;
enc.BitmapTransform.ScaledWidth = newWidth;
// Get image data from the source image
PixelDataProvider pixelData = await decoder.GetPixelDataAsync();
// Copy in all pixel data from source to target
enc.SetPixelData(
decoder.BitmapPixelFormat,
decoder.BitmapAlphaMode,
decoder.PixelWidth,
decoder.PixelHeight,
decoder.DpiX,
decoder.DpiY,
pixelData.DetachPixelData()
);
// Make the encoder process the image
await enc.FlushAsync();
// Write everything to the filestream
await destFileStream.FlushAsync();
}
}
try
{
// Delete the source file
await sourceFile.DeleteAsync();
}
catch(Exception)
{
}
// Return the new path
// including "ms-appdata:///local/cache/"
return string.Format(CachedImagesFolderFullPath + "{0}",
targetPath);
}
I'm currently trying to build an app that involves the user selecting a photo from their library (or taking a photo) and uploading it to Imgur. I have already built a fairly robust C# Imgur client for Windows Forms applications, but unfortunately porting it to the Windows Phone has been a disaster.
Here is the code that I am using:
public void UploadImageAsync(Stream PhotoStream)
{
try
{
WebClient w = new WebClient();
w.Headers["Content-type"] = "application/x-www-form-urlencoded";
string data = "key="+PublicKey+
"&_fake_status=200"+
"&type=base64"+
"&image="+PhotoStreamToBase64(PhotoStream);
w.UploadStringAsync(new Uri("http://api.imgur.com/2/upload", UriKind.Absolute), "POST", data);
}
catch (Exception ex)
{
}
}
string PhotoStreamToBase64(Stream PhotoStream)
{
MemoryStream memoryStream = new MemoryStream();
PhotoStream.CopyTo(memoryStream);
byte[] result = memoryStream.ToArray();
return System.Convert.ToBase64String(result);
}
What is interesting (and frustrating) is that it appears as though everything is working fine, and I receive a successful response after the upload has completed. However, when trying to view the image after being uploaded, the result looks like this: http://i.imgur.com/NWY0R.jpg.
This leads me to believe that somehow the image stream is being converted into the byte array incorrectly, or converted into a base 64 string incorrectly. In any case, I cannot get it to work and I am at a total loss. Does anybody have any idea? Any help would be greatly appreciated.
SpikeX pushed me toward Imgur's C# API example for image uploading. Borrowing the Base64 encoding logic from their example fixed the issue. Here is the now functional PhotoStreamToBase64 method:
string PhotoStreamToBase64(Stream PhotoStream)
{
MemoryStream memoryStream = new MemoryStream();
PhotoStream.CopyTo(memoryStream);
byte[] result = memoryStream.ToArray();
string base64img = System.Convert.ToBase64String(result);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < base64img.Length; i += 32766)
{
sb.Append(Uri.EscapeDataString(base64img.Substring(i, Math.Min(32766, base64img.Length - i))));
}
return sb.ToString();
}