this may be impossible, or rather, not very popular, but I was wondering how I'd go about creating a data file for images, that would actually compress them? Say I had 200MB total of image files, is there some system I can use to store them in a single file, that would compress them to a total size of like 150MB? (Just example numbers, ratios not important).
I know I can encode the images with Base64 and then store them in an SQLite database, but I read that storing images in their encoded forms actually resulted in a slightly larger size than the original image file itself.
I was also thinking of a ZIP file, but I wasn't sure if it could be used as a 'library' as such?
If something like this doesn't exist as a predefined class, could someone lead me on the right track?
This is a mock of what I'm sort of looking for:
class ImageLibrary {
//this is where the code would go for the library?
}
class MyProgram{
public MyProgram()
{
ImageLibrary library = new ImageLibrary();
library.Add(<Image object here, with an ID of sorts>);
library.Add(<Another image object here, also with an ID>);
Load += new FormLoadEventHandler(MyProgram_Load);
}
void MyProgram_Load(object sender, EventArgs e)
{
PictureBox.Image = library.Get(<image id here>);
}
}
I hope this is possible. Else, I'll just put up with a slightly larger file size and Base64 encode them. But, because I have, at the moment, almost 500 images I want to store, a kB saved is a kB earned. :) Also, please don't judge the quality of my code example, it's just a mock up and I wrote it off the cuff.
Cheers, and thankyou in advance.
If save your images as binary files will help this is a code I use to convert them to binary and then save into SQLite:
public byte[] ImageToByte(Image image, System.Drawing.Imaging.ImageFormat format){
using (MemoryStream ms = new MemoryStream())
{
// Convert Image to byte[]
image.Save(ms, format);
byte[] imageBytes = ms.ToArray();
return imageBytes;
}
}
public Image ByteToImage(byte[] imageBytes)
{
// Convert byte[] to Image
MemoryStream ms = new MemoryStream(imageBytes, 0, imageBytes.Length);
ms.Write(imageBytes, 0, imageBytes.Length);
Image image = new Bitmap(ms);
return image;
}
And then to save the binary:
void SaveImage(byte[] image){
string conStringDatosUsuarios = #" Data Source = \Program Files\GPS___CAM\Data\DatosUsuarios.s3db ";
SQLiteConnection con = new SQLiteConnection(conStringDatosUsuarios);
SQLiteCommand cmd = con.CreateCommand();
cmd.CommandText = String.Format("INSERT INTO Users (Foto) VALUES (#0);");
SQLiteParameter p = new SQLiteParameter("#0", System.Data.DbType.Binary);
p.Value = image;
cmd.Parameters.Add(p);
con.Open();
try
{
cmd.ExecuteNonQuery();
}
catch (Exception exc1)
{
MessageBox.Show(exc1.Message);
}
con.Close();
}
Hope it helps
EDIT As you asked, I'm updating with the load image code:
(to convert the byte to image you must use the ByteToImage function)
void LoadImage(string tag){
string query = "SELECT Foto FROM Users;";
string conString = #" conection to your database ";
SQLiteConnection con = new SQLiteConnection(conString);
SQLiteCommand cmd = new SQLiteCommand(query, con);
con.Open();
try
{
SQLiteDataReader rdr = cmd.ExecuteReader();
try
{
while (rdr.Read())
{
pictureBox1.Image = ByteToImage((System.Byte[])rdr[0]);
}
}
catch (Exception exc) { MessageBox.Show(exc.Message); }
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
con.Close();
}
EDIT 2 Try this to see which type of data are you trying to load:
System.Type checkType = rdr[0].GetType();
pictureBox1.Image = ByteToImage((System.Byte[])rdr[0]);
add the first line in your code and put a breakpoint in that line. Check checkType's type. Probably it isn't binary. Let me know the result to help you.
Check out this article: http://msdn.microsoft.com/en-us/library/aa479502.aspx
I hope it helps. An idea for accessing files from a ZIP. It's not ready-to-use solution, but a nice concept explanation.
Related
I've got a pesky problem with gzipstream targeting .Net 3.5. This is my first time working with gzipstream, however I have modeled after a number of tutorials including here and I'm still stuck.
My app serializes a datatable to xml and inserts into a database, storing the compressed data into a varbinary(max) field as well as the original length of the uncompressed buffer. Then, when I need it, I retrieve this data and decompress it and recreates the datatable. The decompress is what seems to fail.
EDIT: Sadly after changing the GetBuffer to ToArray as suggested, my issue remains. Code Updated below
Compress code:
DataTable dt = new DataTable("MyUnit");
//do stuff with dt
//okay... now compress the table
using (MemoryStream xmlstream = new MemoryStream())
{
//instead of stream, use xmlwriter?
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();
settings.Encoding = Encoding.GetEncoding(1252);
settings.Indent = false;
System.Xml.XmlWriter writer = System.Xml.XmlWriter.Create(xmlstream, settings);
try
{
dt.WriteXml(writer);
writer.Flush();
}
catch (ArgumentException)
{
//likely an encoding issue... okay, base64 encode it
var base64 = Convert.ToBase64String(xmlstream.ToArray());
xmlstream.Write(Encoding.GetEncoding(1252).GetBytes(base64), 0, Encoding.GetEncoding(1252).GetBytes(base64).Length);
}
using (MemoryStream zipstream = new MemoryStream())
{
GZipStream zip = new GZipStream(zipstream, CompressionMode.Compress);
log.DebugFormat("Compressing commands...");
zip.Write(xmlstream.GetBuffer(), 0, xmlstream.ToArray().Length);
zip.Flush();
float ratio = (float)zipstream.ToArray().Length / (float)xmlstream.ToArray().Length;
log.InfoFormat("Resulting compressed size is {0:P2} of original", ratio);
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandText = "INSERT INTO tinydup (lastid, command, compressedlength) VALUES (#lastid,#compressed,#length)";
cmd.Connection = db;
cmd.Parameters.Add("#lastid", SqlDbType.Int).Value = lastid;
cmd.Parameters.Add("#compressed", SqlDbType.VarBinary).Value = zipstream.ToArray();
cmd.Parameters.Add("#length", SqlDbType.Int).Value = xmlstream.ToArray().Length;
cmd.ExecuteNonQuery();
}
}
Decompress Code:
/* This is an encapsulation of what I get from the database
public class DupUnit{
public uint lastid;
public uint complength;
public byte[] compressed;
}*/
//I have already retrieved my list of work to do from the database in a List<Dupunit> dupunits
foreach (DupUnit unit in dupunits)
{
DataSet ds = new DataSet();
//DataTable dt = new DataTable();
//uncompress and extract to original datatable
try
{
using (MemoryStream zipstream = new MemoryStream(unit.compressed))
{
GZipStream zip = new GZipStream(zipstream, CompressionMode.Decompress);
byte[] xmlbits = new byte[unit.complength];
//WHY ARE YOU ALWAYS 0!!!!!!!!
int bytesdecompressed = zip.Read(xmlbits, 0, unit.compressed.Length);
MemoryStream xmlstream = new MemoryStream(xmlbits);
log.DebugFormat("Uncompressed XML against {0} is: {1}", m_source.DSN, Encoding.GetEncoding(1252).GetString(xmlstream.ToArray()));
try{
ds.ReadXml(xmlstream);
}catch(Exception)
{
//it may have been base64 encoded... decode first.
ds.ReadXml(Encoding.GetEncoding(1254).GetString(
Convert.FromBase64String(
Encoding.GetEncoding(1254).GetString(xmlstream.ToArray())))
);
}
xmlstream.Dispose();
}
}
catch (Exception e)
{
log.Error(e);
Thread.Sleep(1000);//sleep a sec!
continue;
}
Note the comment above... bytesdecompressed is always 0. Any ideas? Am I doing it wrong?
EDIT 2:
So this is weird. I added the following debug code to the decompression routine:
GZipStream zip = new GZipStream(zipstream, CompressionMode.Decompress);
byte[] xmlbits = new byte[unit.complength];
int offset = 0;
while (zip.CanRead && offset < xmlbits.Length)
{
while (zip.Read(xmlbits, offset, 1) == 0) ;
offset++;
}
When debugging, sometimes that loop would complete, but other times it would hang. When I'd stop the debugging, it would be at byte 1600 out of 1616. I'd continue, but it wouldn't move at all.
EDIT 3: The bug appears to be in the compress code. For whatever reason, it is not saving all of the data. When I try to decompress the data using a third party gzip mechanism, I only get part of the original data.
I'd start a bounty, but I really don't have much reputation to give as of now :-(
Finally found the answer. The compressed data wasn't complete because GZipStream.Flush() does absolutely nothing to ensure that all of the data is out of the buffer - you need to use GZipStream.Close() as pointed out here. Of course, if you get a bad compress, it all goes downhill - if you try to decompress it, you will always get 0 returned from the Read().
I'd say this line, at least, is the most wrong:
cmd.Parameters.Add("#compressed", SqlDbType.VarBinary).Value = zipstream.GetBuffer();
MemoryStream.GetBuffer:
Note that the buffer contains allocated bytes which might be unused. For example, if the string "test" is written into the MemoryStream object, the length of the buffer returned from GetBuffer is 256, not 4, with 252 bytes unused. To obtain only the data in the buffer, use the ToArray method.
It should be noted that in the zip format, it first works by locating data stored at the end of the file - so if you've stored more data than was required, the required entries at the "end" of the file don't exist.
As an aside, I'd also recommend a different name for your compressedlength column - I'd initially taken it (despite your narrative) as being intended to store, well, the length of the compressed data (and written part of my answer to address that). Maybe originalLength would be a better name?
I want to compare an uploaded pic with all images in database , if it is matching then should display data with image , the compare is simple by converting each image to binary see this
I am using this code but its not working:
protected void CompareImages(object sender, EventArgs e)
{
if (this.FileUpload1.HasFile)
{
Stream fs = FileUpload1.PostedFile.InputStream;
BinaryReader br = new BinaryReader(fs);
Byte[] bytes = br.ReadBytes((Int32)fs.Length);
string constr = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
using (SqlConnection conn = new SqlConnection(constr))
{
string sqlQuery = "SELECT * from [MostWanted] WHERE Photo = #FileName";
using (SqlCommand cmd = new SqlCommand(sqlQuery, conn))
{
cmd.Parameters.AddWithValue("#FileName", Path.GetFileName(this.FileUpload1.PostedFile.FileName));
conn.Open();
Byte[] bytes2 = (Byte[])cmd.ExecuteScalar();
conn.Close();
if (bytes2 != null)
{
if (bytes.Length == bytes2.Length)
{
ClientScript.RegisterStartupScript(this.GetType(), "alert", "alert('Equal Images')", true);
}
else
{
ClientScript.RegisterStartupScript(this.GetType(), "alert", "alert('Images are not equal')", true);
}
}
else
{
ClientScript.RegisterStartupScript(this.GetType(), "alert", "alert('File Not found in the table')", true);
}
}
}
}
}
Unless those images are literally copies of the same file, a simple binary comparision will not work. Even one bit difference or a different file format will utterly break it. And that that point recognising them as the same goes into Artificial Intelligence Research.
If those files are the same down to the last byte, you could relevantly speed up the check by storing a hashvalue for every image. Compare the hash of the image you try to add with all existing image hashes to pre-filter wich ones can not be the same. You still should do a binary comparision, as even with hash values you can have wrong positives/colissions (two different inputs having the same output) happen.
As you are also planning to store a lot of images, I think the filestream atribute might help: https://www.simple-talk.com/sql/learn-sql-server/an-introduction-to-sql-server-filestream/
Have column named data varbinary(max) to store the image file
Query statement need not have where condition
Remove the add parameter line. It works perfectly then, by comparing binary data alone
So I collect a varbinary(MAX) value from a database where an image is stored.
It gets converted to byte[], then the aim is to display this in an image control.
This is where I read from the database
public TemplateData(SqlDataReader dr)
{
initialiseData();
if (dr.HasRows)
{
Logo = (byte[])dr["Logo"];
//Logo = dr["Logo"].ToString();
TemplateId = dr["TemplateId"].ToString();
Comment = dr["Comment"].ToString();
SchemeCode = dr["SchemeCode"].ToString();
Version = dr["Version"].ToString();
}
}
This is where the values are displayed into the corresponding controls
protected void ddSchemeCode_SelectedIndexChanged(object sender, EventArgs e)
{
if (ddSchemeCode.SelectedIndex > 0)
{
// Existing Data to load from database
TemplateData temp = DataClass.ReturnData(ddSchemeCode.SelectedItem.Text);
if (temp != null)
{
txtVersion.Text = temp.Version;
txtComment.Text = temp.Comment;
txtSchemeCode.Text = temp.SchemeCode;
txtTemplateId.Text = temp.TemplateId;
img.Src = temp.Logo;
}
So at the moment I am passing a byte[] into the source of an image control, where it would instead like a string. I've tried converting it to a string with Convert.ToBase64String(Logo) and ToString(Logo) but these do not work.
Any help is greatly appreciated. Cheers guys and gals.
Try converting the byte array to image and assign it to picturebox as below,
try
{
using (MemoryStream mStream = new MemoryStream())
{
// where pData is your byte array
mStream.Write(pData, 0, Convert.ToInt32(pData.Length));
Image originalImage = Image.FromStream(mStream);
picBox.Image = originalImage;
}
}
catch (Exception ex)
{
}
Hope it helps.
As you may have noticed, you cannot "print" an image in a webpage. You need to get a little bit creative now.
What you want to look into is Response.BinaryWrite. More information about that, here: https://msdn.microsoft.com/en-us/library/system.web.httpresponse.binarywrite%28v=vs.110%29.aspx
You will probably also need a generic ashx handler. Here is an example of how to show a picture using a handler: http://www.dotnetperls.com/ashx
My suggestion would be to store the logo as a byte[] into the http session. Put the source of the image to theHttpHandlerYourGonnaCreate.ashx. You can then binary write the byte[] you've stored into the session there.
Hope this helps!
As Michael shows here, you can convert the byte array to a Bitmap object with something like this:
Bitmap bitmap = null;
using (MemoryStream imageStream = new MemoryStream(imageData))
{
bitmap = new Bitmap(imageStream);
}
It isn't entirely clear what you're using for a control to show the image, but any control that can display an image should be able to take a Bitmap or Image object.
I have a program to select an image and to set that selected image in a picture box then convert the image in the image box to byte array and save sql server data base in a image type column.
In browse button click event I'm selecting the image file like this.
OpenFileDialog fop = new OpenFileDialog();
fop.InitialDirectory = #"Desktop";
fop.Filter = "image files|*.jpg;*.png;*.gif";
if (fop.ShowDialog() == DialogResult.OK)
{
FileStream FS = new FileStream(#fop.FileName, FileMode.Open, FileAccess.Read);
pbPartySymbol.Image = new Bitmap(fop.FileName);
MessageBox.Show("Image Imported Successfully!!", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
After selecting the image and setting it the picture box image I'm converting the picture box image to byte array in the save button click event and saving the byte array to database.
public byte[] imageToByteArray(System.Drawing.Image imageIn)
{
MemoryStream ms = new MemoryStream();
imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
return ms.ToArray();
}
I'm calling the method like this.
byte[] myArr1 = imageToByteArray(pbPartySymbol.Image);
and I'm passing this byte array to the data base. and it saves too. But ALL the added images are saved as like this.* 0x53797374656D2E427974655B5D* Saved images cannot be get back to the picture box in the reading operation. What am I doing wrong in the SAVING?
Here's what I do in saving operation in the form.
Party ptObj = new Party(myArr1);
if (new PartyOP().saveParty(ptObj))
{
MessageBox.Show("NEW data added");
}
In my business operation layer this is my code.
public Boolean saveParty(Party ptObj)
{
string query1 = "EXEC insertToParty'" + ptObj.PTSymARR + "'";
return (new DataAccessLayer().executeNonQueries(query1));
}
Here's how I have set the property in the Party class.
class Party
{
public Party() { }
public Party(byte[] ptSym)
{
this._PTSymARR = ptSym;
}
public byte[] PTSymARR
{
get { return _PTSymARR; }
set { _PTSymARR = value; }
}
}
here's my stored procedure.
CREATE PROCEDURE insertToParty
(
#ptSymbol image
)
AS
BEGIN
BEGIN TRANSACTION
SET NOCOUNT ON;
--Query
INSERT INTO Party(PTSYM)
VALUES (#ptSymbol);
Data type of PTSYM is image data type.
That Hex string tranlates to System.Byte[].
You add the Byte-Array to the string starting with EXEC insertToParty so .Net assumes you want a string representation of that variable. Unless specified it calls .ToString wich gives you .... "System.Byte[]".
Edit: Your stored procedure does not use hex encoding on insert. image data shows in hex when viewed in SQL Studio etc.
If you would encode your byte array to a string using base64 or hex when inserting and decode that when you read the data you only had to worry about the increased storage, but I recommend that you change your db acces from a dynamic SELECT to a prepared statement with parameters:
SqlCommand command = new SqlCommand("Exec InsertToParty(#blob)");
command.Parameters.AddWithValue("#blob", ptObj.PTSymARR);
command.ExecuteNonQuery();
I have created a web page where I have used Image control to display the image. Along with this control I have some labels and input box also
Below is the code which I have used to save the image to database
byte[] imageSize = new byte[FileUpload1.PostedFile.ContentLength];
HttpPostedFile uploadedImage = FileUpload1.PostedFile;
uploadedImage.InputStream.Read(imageSize, 0, (int)FileUpload1.PostedFile.ContentLength);
SqlParameter UploadedImage = new SqlParameter("#image", SqlDbType.Image, imageSize.Length);
UploadedImage.Value = imageSize;
string sql = "insert into imageDB(Image) values (#image)";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.Add(UploadedImage);
cmd.ExecuteNonQuery();
}
and below is the code I have used to retrieve the image from database
byte[] rawImg = (byte[])rdr["image"];
MemoryStream Stream = new MemoryStream();
Stream.Write(rawImg, 0, rawImg.Length);
Bitmap Display_Picture = new Bitmap(Stream);
//after this no idea how to proceed
I have read some links all are suggesting we can not set this byte information to Image control.
Let me know if my way of retrieving the image from data base is right, if its right, what type of control I should use, so that image which has been retrieved from database can be displayed on web page
Is my way of retrieving the image from database right?
I would use the following code instead (sorry not tested):
byte[] imageBytes = Convert.FromBase64String(rdr["image"]);
MemoryStream stream = new MemoryStream(imageBytes);
Image image = Image.FromStream(stream);
How do I bind the image to an asp.net image control?
I would create an HttpHandler which gets and returns the image. Then bind the ImageUrl property on the asp:Image to the url of the HttpHandler.
How you can do this you can see in the answer of Dale Ragan over here: How to bind a MemoryStream to asp:image control?
First, in your example your have to reset the Position inside your stream to 0: Stream.Position = 0;
Second, you have to write your image to a local cache folder of your web service that is part of your web application. You image control can have a Source-reference (img.src) to the address of that image.
Or... you can create an AXD-file / HttpHandler. An AXD file is a file, just like an ASPX, but specialized in returning any type of data, like an image.
For more info, see: http://blog.kurtschindler.net/post/using-httphandlers-to-serve-image-files
To retrieve image, and show on asp.net Image control, you can
cn.Open();
SqlCommand cm = new SqlCommand("select * from ImageCollection where img_id='" + DropDownList1.SelectedItem.ToString() + "'", cn);
SqlDataAdapter da = new SqlDataAdapter(cm);
SqlDataReader dr = cm.ExecuteReader();
try
{
if (dr.Read())
{
string image1 = Convert.ToString(DateTime.Now.ToFileTime());
FileStream fs1 = new FileStream(image1, FileMode.CreateNew, FileAccess.Write);
byte[] bimage1 = (byte[])dr["passport_photo"];
fs1.Write(bimage1, 0, bimage1.Length - 1);
fs1.Flush();
Image1.ImageUrl = "~/images/"+DropDownList1.SelectedItem.ToString();
}
dr.Close();
cn.Close();
}
catch (Exception ex)
{
throw ex;
}
Alternative solution:
Implement a handler to serve the image. Have a look at the System.Web.IHttpHandler interface.
The idea would be to write an image tag in your page along the lines of:
<asp:image runat="server" imageurl="~/Image.ashx?ID=xxx" />
Then in your handler implementation, get the id from the querystring, retrieve the image as you're already doing and then write directly to the response stream.