I am making a camera app on unity for learning purpose. I followed two tutorial and got my cam running Now here is a code that I found on stack overflow, The issue is its not saving the output on phone.
public void Pic()
{
StartCoroutine(TakePhoto());
}
IEnumerator TakePhoto() // Start this Coroutine on some button click
{
// NOTE - you almost certainly have to do this here:
yield return new WaitForEndOfFrame();
Texture2D photo = new Texture2D(backCam.width, backCam.height);
photo.SetPixels(backCam.GetPixels());
photo.Apply();
//Encode to a PNG
byte[] bytes = photo.EncodeToPNG();
//Write out the PNG. Of course you have to substitute your_path for something sensible
File.WriteAllBytes(Application.dataPath + "photo.png", bytes);
}
I have attached Pic() to a button.
I am just learning these so might be making some stupid mistake.
It needs to be /photo.png, otherwise you are just appending a filename to foldername
You should also consider switching to persistentDataPath and validating it for file io (checking if it exists is a good practice)
For Paths you should always use Path.Combine instead of direct string concatenation
Path.Combine(Application.dataPath, "photo.png")
It is also possible that the folder or file simply don't exist yet so you could check that and create them in that case before writing
var filePath = Path.Combine(Application.dataPath, "photo.png");
if (!Directory.Exists(Application.dataPath))
{
Directory.CreateDirectory(Application.dataPath);
}
if (!File.Exists(filePath))
{
File.Create(filePath);
}
//Write out the PNG. Of course you have to substitute your_path for something sensible
File.WriteAllBytes(filePath, bytes);
You need to add a slash before your file name.
File.WriteAllBytes($"{Application.dataPath}/photo.png", bytes);
Related
I am currently maintaining an old project which I would very much like to keep the old code and avoid refactoring of the old code if it's possible. To upload it to google play store, I have used play asset delivery PAD system which is requiring asset bundle system.
I am able to successfully loading asset bundles in an asynchronous way with coroutines following the tutorials.
My question is that; is it possible to access bundled assets without changing original way of accessing such as
UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/Prefabs/Game/Tiles/Tile.prefab", typeof(Tile)) as Tile;
instead of implementing the following code?
var assets = AssetDatabase.FindAssets("t:Sprite",
new[] {"Assets/Images"});
foreach (var guid in assets)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
Sprite loadedSprite = null;
if (imageDictionary.TryGetValue(assetPath, out loadedSprite)
== false)
{
loadedSprite = AssetDatabase.LoadAssetAtPath<Sprite>(
assetPath);
if (loadedSprite != null)
{
imageDictionary.Add(assetPath, loadedSprite);
AddImageToList(assetPath, loadedSprite);
}
}
}
I am currently using 2 asset bundles consists of texture and spine animation folders.I have created 2 asset bundles which are texture folder (110 MB) and spine animation bundle (23 MB) and base asset. When I created the aab file, file size increased to 370 MB.
(I know that I need to split my bundles to match play store requirement of 150 MB for base assets but this is a different issue.)
And also, my educated guess is that, unity is not only adding asset bundles but also adding assets in old fashioned way which is an issue might relate to this question.
I was also facing the same problem.
The initially built AAB file exceeded 150MB and could not be uploaded to the store.
But I found a way to upload.
order
Put the build scene in "File - BuildSetting - Scene In Build".
Check "Build App Bundle" in "File - BuildSetting".
Close BuildSetting and click "Google - Build Android App Bundle"
Build.
(In this process, I did not create an AssetBundle. I used the original program as it was)
Ignore the large size alarm in the middle of the build.
When the build is completed, the size will be larger than 150MB.
Don't worry about the size of the build file and upload it to the Google console.
"Google - Build Android App Bundle" The file built with this goes up.
I hope the problem is resolved.
#Alp, Google's PAD tutorial is quite limited and it only guesses you're trying to access textures, which might not be always the case.
You can actually access the bundles directly, and load them into memory, and if you've packed your prefab as an assetBundle, when you load your assetBundle this way you can just cast it to a GameObject later and then use the prefab as a GameObject to instantiate it or do whatever you need.
There is only one official way of loading asset bundles if you packed them as install-time,
public static IEnumerator LoadAssetBundleFromMemoryAsync(string packName, string assetName, Action<AssetBundle> callback)
{
var packRequest = PlayAssetDelivery.RetrieveAssetPackAsync(packName);
while (!packRequest.IsDone)
{
yield return null;
}
AssetLocation location = packRequest.GetAssetLocation(assetName);
long size = (long)location.Size;
long offset = (long)location.Offset;
using (Stream stream = File.OpenRead(location.Path))
{
byte[] bytes = new byte[size];
stream.Seek(offset, SeekOrigin.Begin);
stream.Read(bytes, 0, bytes.Length);
AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(bytes);
yield return request;
callback(request.assetBundle);
}
}
And then, I also figured out an unofficial way of loading asset bundles directly from file. It turns out that the location we get for a given assetBundle is going to be inside the split apk module with the same name as the assetPack.
But if we just replace the split_pack.apk with base.apk, we can actually read it directly from the File without needing to actually pre-load the bundle into memory. Load from memory will put it into memory in advance, while using the LoadFromFile way, you can actually load the bundle to memory when it's needed.
Something like this:
public static IEnumerator LoadAssetBundleFromFileAsync (string packName, string assetName, Action<AssetBundle> callback)
{
var packRequest = PlayAssetDelivery.RetrieveAssetPackAsync(packName);
while (!packRequest.IsDone)
{
yield return null;
}
AssetLocation location = packRequest.GetAssetLocation(assetName);
string path = location.Path.Replace($"split_{packName}.apk", "base.apk");
string basePath = $"{path}!assets/assetpack/{assetName}";
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(basePath);
while (!request.isDone)
{
yield return null;
}
callback(request.assetBundle);
}
You could also maybe only return the 'assetPath' and load it later.
These are only for install-time asset packs.
If you're trying to load any file from a fast-follow or on-demand asset pack, then all you need is the location.Path to load it from, as a raw file.
public static IEnumerator GetAssetLocationFromAssetPack (string packName, string assetName, Action<string> callback)
{
PlayAssetPackRequest packRequest = PlayAssetDelivery.RetrieveAssetPackAsync(packName);
while (!packRequest.IsDone)
{
yield return null;
}
AssetLocation location = packRequest.GetAssetLocation(assetName);
callback(location.Path);
}
I hope it helps, and be wary that any folder or assets contained in your StreamingAssets if also set to be in an assetPack will be added in double, everything contained in StreamingAssets will be in your base.apk (within the AAB), so if you have something from StreamingAssets that will actually be loaded from a pack, delete it from Streaming to avoid an AAB bigger than expected.
I'm struggling a little with images on the Azure platform under dotnet core and I'm hoping someone can make a sensible suggestion.
Simple enough premise: user uploads image, saved in a database as base64 (about to move to Azure storage blob, but that's irrelevant to this). Later on, site owner comes along and clicks a button to get all these images down in a ZIP file. In the old days of .net framework this was easy. Now I seem to be hitting a set of 'yes, but' comments.
Yes, there's system.drawing.image but you can't use that because it's not in dotnet core (until recently).
Yes, you can use CoreCompat but it doesn't work on Azure because in Web Applications there's no support for GDI+.
Yes, even if I could, I'm developing on a Mac so it won't work locally as far as I can see.
I have tried beta4 of ImageSharp without a lot of success. It's random - sometimes it works, sometimes it just throws OutOfMemoryException.
I have tried SkiaSharp but similar results; sometimes it works, sometimes it spits out random error messages.
I'm not doing anything fancy in terms of processing, no resizing or anything. It should be a case of load file to byte array from Convert.FromBase64String, create Zip file entry, ultimately spit out zip file. The ZIP portion is fine, but I need something decent that can do the image work.
Here's a bit of code:
if(!string.IsNullOrEmpty(del.Headshot))
{
var output=SKImage.FromBitmap(SKBitmap.Decode(Convert.FromBase64String(del.Headshot)));
MemoryStream savedFile=new MemoryStream();
output.Encode(SKEncodedImageFormat.Jpeg, 100).SaveTo(savedFile);
string name=$"{del.FirstName} {del.LastName} - {del.Company}".Trim(Path.GetInvalidFileNameChars()) + "_Headshot.jpg";
ZipArchiveEntry entry=zip.CreateEntry(name);
using(Stream entryStream=entry.Open())
{
entryStream.Write(savedFile.ToArray(), 0, Convert.ToInt32(savedFile.Length));
}
output.Dispose();
savedFile.Dispose();
}
Can anyone give me a sensible suggestion for a library that can handle images, cross-platform and on Azure, before I pull out what little hair remains!
Thanks
EDIT: The first answer is technically correct, I don't need anything else. However, I might have been a bit wrong when I said I wasn't doing any image manipulation. Because it's all base64 without a filename being stored anywhere, I've no idea what sort of file it is. I'm therefore saving each one as JPEG to ensure that I can always output that file type and extension. Users I guess could be uploading JPG / PNG or even GIF.
Technically you do not need any of those other imaging (unless you are doing more that just zipping the content). Convert the base64 to byte array and pass that to the zip file. No need to save to disk just to read it back again for zipping.
//...
if(!string.IsNullOrEmpty(del.Headshot)) {
var imageBytes = Convert.FromBase64String(del.Headshot);
string name = $"{del.FirstName} {del.LastName} - {del.Company}".Trim(Path.GetInvalidFileNameChars()) + "_Headshot.jpg";
ZipArchiveEntry entry = zip.CreateEntry(name);
using(Stream entryStream = entry.Open()) {
entryStream.Write(imageBytes, 0, imageBytes.Length));
}
}
//...
Also using a minor hack for known image types when converted to base64
public static class ImagesUtility {
static IDictionary<string, string> mimeMap =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "IVBOR", "png" },
{ "/9J/4", "jpg" },
//...add others
};
/// <summary>
/// Extract image file extension from base64 string.
/// </summary>
/// <param name="base64String">base64 string.</param>
/// <returns>file extension from string.</returns>
public static string GetFileExtension(string base64String) {
var data = base64String.Substring(0, 5);
var extension = mimeMap[data.ToUpper()];
return extension;
}
}
You could try to determine the file extension from its prefix
if(!string.IsNullOrEmpty(del.Headshot)) {
var imageBytes = Convert.FromBase64String(del.Headshot);
var ext = ImagesUtility.GetFileExtension(del.Headshot) ?? "jpg";
string name = $"{del.FirstName} {del.LastName} - {del.Company}".Trim(Path.GetInvalidFileNameChars()) + $"_Headshot.{ext}";
ZipArchiveEntry entry = zip.CreateEntry(name);
using(Stream entryStream = entry.Open()) {
entryStream.Write(imageBytes, 0, imageBytes.Length));
}
}
Now ideally, if you are able to control the type of image uploaded, then you should also validate and do what ever image processing when the data is being saved along with any needed metadata (ie content type). That way when extracting it from storage, you can be confident that it is the correct type and size. That saves you having to worry about that later on.
Aspose.Drawing and Aspose.Imaging can handle images and cross-platform running on .NET Core (I'm one of the developers).
I have build a program which allows me to insert comment and the title of an Image through System.Image.Drawing so right now, I have trouble trying to overwrite the existing Jpeg file with the one that has comment and title added into it, but I am encountering error while deleting the file, so I'm not sure what to do as I have tried disposing the file but I cant saved it in that case, due to the fact that I disposed it too early, but I cant saved it because the existing file name is not deleted so I'm kinda stuck in the middle right now.
Here are my codes for it:
public string EditComment(string OriginalFilepath, string newFilename)
{
image = System.Drawing.Image.FromFile(OriginalFilepath);
PropertyItem propItem = image.PropertyItems[0];
using (var file = System.Drawing.Image.FromFile(OriginalFilepath))
{
propItem.Id = 0x9286; // this is the id for 'UserComment'
propItem.Type = 2;
propItem.Value = System.Text.Encoding.UTF8.GetBytes("HelloWorld\0");
propItem.Len = propItem.Value.Length;
file.SetPropertyItem(propItem);
PropertyItem propItem1 = file.PropertyItems[file.PropertyItems.Count() - 1];
file.Dispose();
image.Dispose();
string filepath = Filepath;
if (File.Exists(#"C:\Desktop\Metadata"))
{
System.IO.File.Delete(#"C:\Desktop\Metadata");
}
string newFilepath = filepath + newFilename;
file.Save(newFilepath, ImageFormat.Jpeg);//error appears here
return filepath;
}
}
The Error shown are:
An exception of type 'System.ArgumentException' occurred in System.Drawing.dll but was not handled in user code
Additional information: Parameter is not valid.
The problem is that opening an image from file locks the file. You can get around that by reading the file into a byte array, creating a memory stream from that, and then opening the image from that stream:
public string EditComment(string originalFilepath, string newFilename)
{
Byte[] bytes = File.ReadAllBytes(originalFilepath);
using (MemoryStream stream = new MemoryStream(bytes))
using (Bitmap image = new Bitmap(stream))
{
PropertyItem propItem = image.PropertyItems[0];
// Processing code
propItem.Id = 0x9286; // this is the id for 'UserComment'
propItem.Type = 2;
propItem.Value = System.Text.Encoding.UTF8.GetBytes("HelloWorld\0");
propItem.Len = propItem.Value.Length;
image.SetPropertyItem(propItem);
// Not sure where your FilePath comes from but I'm just
// putting it in the same folder with the new name.
String newFilepath;
if (newFilename == null)
newFilepath = originalFilePath;
else
newFilepath = Path.Combine(Path.GetDirectory(originalFilepath), newFilename);
image.Save(newFilepath, ImageFormat.Jpeg);
return newFilepath;
}
}
Make sure you do not dispose your image object inside the using block as you did in your test code. Not only does the using block exist exactly so you don't have to dispose manually, but it's also rather hard to save an image to disk that no longer exists in memory. Similarly, you seem to open the image from file twice. I'm just going to assume all of those were experiments to try to get around the problem, but do make sure to clean those up.
The basic rules to remember when opening images are these:
An Image object created from a file will lock the file during the life cycle of the image object, preventing the file from being overwritten or deleted until the image is disposed.
An Image object created from a stream will need the stream to remain open for the entire life cycle of the image object. Unlike with files, there is nothing actively enforcing this, but after the stream is closed, the image will give errors when saved, cloned or otherwise manipulated.
Contrary to what some people believe, a basic .Clone() call on the image object will not change this behaviour. The cloned object will still keep the reference to the original source.
Note, if you actually want a usable image object that is not contained in a using block, you can use LockBits and Marshal.Copy to copy the byte data of the image object into a new image with the same dimensions and the same PixelFormat, effectively making a full data clone of the original image. (Note: I don't think this works on animated GIF files) After doing that, you can safely dispose the original and just use the new cleanly-cloned version.
There are some other workarounds for actually getting the image out, but most of them I've seen aren't optimal. These are the two most common other valid workarounds for the locking problem:
Create a new Bitmap from an image loaded from file using the Bitmap(Image image) constructor. This new object will not have the link to that file, leaving you free to dispose the one that's locking the file. This works perfectly, but it changes the image's colour depth to 32-bit ARGB, which might not be desired. If you just need to show an image on the UI, this is an excellent solution, though.
Create a MemoryStream as shown in my code, but not in a using block, leaving the stream open as required. Leaving streams open doesn't really seem like a good idea to me. Though some people have said that, since a MemoryStream is just backed by a simple array, and not some external resource, the garbage collector apparently handles this case fine...
I've also seen some people use System.Drawing.ImageConverter to convert from bytes, but I looked into the internals of that process, and what it does is actually identical to the last method here, which leaves a memory stream open.
I want to remove blank lines from my file, foe that I am using code below.
private void ReadFile(string Address)
{
var tempFileName = Path.GetTempFileName();
try
{
//using (var streamReader = new StreamReader(Server.MapPath("~/Images/") + FileName))
using (var streamReader = new StreamReader(Address))
using (var streamWriter = new StreamWriter(tempFileName))
{
string line;
while ((line = streamReader.ReadLine()) != null)
{
if (!string.IsNullOrWhiteSpace(line))
streamWriter.WriteLine(line);
}
}
File.Copy(tempFileName, Address, true);
}
finally
{
File.Delete(tempFileName);
}
Response.Write("Completed");
}
But the problem is my file is too large (8 lac lines ) so its taking lot of time. So is there any other way to do it faster?
Instead of doing a ReadLine(), I would do a StreamReader.ReadToEnd() to load the entire file into memory, then do a line.Replace("\n\n","\n") and then do a streamWrite.Write(line) to the file. That way there is not a lot of thrashing, either memory or disk, going on.
The best solution may well depend on the disk type - SSDs and spinning rust behave differently. Your current approach has the advantage over Steve's answer of being able to do processing (such as encoding text data back as binary) while data is still coming off the disk. (With buffering and background IO, there's a lot of potential asynchrony here.) It's definitely worth trying both approaches. (Obviously your approach uses less memory, too.)
However, there's one aspect of your code which is definitely suboptimal: creating a copy of the results. You don't need to do that. You can use file moves instead which are a lot more efficient, assuming they're all in the same drive. To make sure you don't lose data, you can do two moves and a delete:
Move the old file to a backup filename
Move the new file to the old filename
Delete the backup filename
It looks like this is what File.Replace does for you, which makes it considerably simpler, and also preserves the original metadata.
If something goes wrong after the first move, you're left without the "proper" file from either old or new, but you can detect that and use the backup filename to read next time.
Of course, if this is meant to happen as part of a web request, you may want to do all the processing in a background task - processing 800,000 lines of text is likely to take longer than you really want a web request to take...
I'm very new to this stuff of saving images to the DB, and even when I thought it was very straight forward, it wasn't. What I'm trying to do is read and image file from the same computer in any format, display it in a picture box, and then convert the image to bytes to save it in the DB. Until now, I can display the image in the picture box, but I can't convert the image to bytes. Here's my code:
private void DisplayImage()
{
if (openFileDialog.ShowDialog(this) == DialogResult.OK)
{
try
{
Stream file;
if ((archivo = openFileDialog.OpenFile()) != null)
{
using (file)
{
pictureBox.Image = Image.FromStream(file);
}
}
}
catch (Exception ex)
{
...
}
}
}
That's a simple method that just displays the image in the picture box. The real problem is with the following method:
public static byte[] ConvertImageToBytes(Image image)
{
if (image != null)
{
MemoryStream ms = new MemoryStream();
using (ms)
{
image.Save(ms, ImageFormat.Bmp);
byte[] bytes = ms.ToArray();
return bytes;
}
}
else
{
return null;
}
}
When it tries to save the image to the memory stream, I get the error:
System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI+.
Any ideas on what's happening?
You should use the RawFormat property of the original image as a parameter to the Save method, not default to a Bitmap. This will avoid image format type errors. eg:
image.Save(ms, image.RawFormat);
ms.Position = 0;
byte [] bytes=ms.ToArray();
I'd advise actually saving images to the file-system and simply storing the file path (preferably relative) in the database.
BLOBs (ie images etc) in a database cannot be indexed, are often stored in a secondary, slower access database area and will quickly blow out the size of the database (slower backups etc).
Cant you simply Read the file and load it to a byte[] using the File class:
byte[] imgData = System.IO.File.ReadAllBytes(#"C:\My Pic\Myfile.jpg");
You can pick the image path from your Open Dialog box.
That particular exception generally means that you are trying to save the image as the wrong format. In your code you specify ImageFormat.Bmp - is it actually a bitmap image, or did you perhaps load it from a JPEG or PNG? Attempting to save as a different format from the one you loaded will fail with ExternalException, as specified in the documentation.
Incidentally, I don't recommend storing images in a database and I believe most people here will agree. Databases may be able to handle this task but they are not optimized for it, and you end up hurting the performance of both your database and your application. Unless you are using SQL Server 2008 FILESTREAM columns, it is more efficient to store images on the file system.
It may be stupid to answer my own question, but I just found out that if I want to convert the Image object to bytes, I have to leave the original stream open. I saw this issue in another page I can't quite remember, and I tested it by leaving the stream open and it's true. So the format wasn't the problem. But I will take the advice of all of you and store the images in a separate directory. Thanks for your help guys!
The problem with this is that stream must be open during the lifetime of of the image otherwise will fail.
One solution that worked for me is just to create a copy of the image like this:
using (var ms = new MemoryStream(bytes))
{
_image = new Bitmap(Image.FromStream(ms));
}