locked
Handler for Photos.... RRS feed

  • Question

  • User1923026465 posted

    Anyone willing to share what needs to be changed in order to store photos on the disk rather than in the db?

    Please advise

    Shane L. Hackney

    Thursday, March 9, 2006 9:21 AM

All replies

  • User1923026465 posted

    Bump....

    No one has any help for me on this?

    Monday, March 13, 2006 10:01 AM
  • User-1653776603 posted

    The photo functionality is mostly hanlded through the code in the code directory, custom image user controls and the imagefetch.ashx. My recommendation would be to retain the table for images, but instead of storing the image in the database, store the path to the image instead. That way, all you should need to modify is the upload code and thumb control. Nowhere else in the site should point at the ashx, so if you tweak it, you should be able to change the image functionality in one central place.

    Sam

    Monday, March 13, 2006 12:26 PM
  • User1923026465 posted
    Does anyone have an example of code I would change to have the pictures save to a file rather than to the DB? 
    Monday, March 13, 2006 12:28 PM
  • User506323975 posted

    Don't know if it is the same routine as it is in the personal site starterkit. It will give you the idea how it can be done. I decided i wanted to store only the original picture and a thumbnail. All the other size are created on the fly when requested. As the thumbnails will be requested a lot it is better to store them as files.

    public static void AddPhoto(int AlbumID, string Caption, byte[] BytesOriginal) {
    	using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["Personal"].ConnectionString)) {
    		using (SqlCommand command = new SqlCommand("AddPhoto", connection)) {
    		command.CommandType = CommandType.StoredProcedure;
                    SqlParameter r = command.Parameters.Add("@ReturnValue", SqlDbType.Int);
                    r.Direction = ParameterDirection.ReturnValue;
                    command.Parameters.Add(new SqlParameter("@AlbumID", AlbumID));
    		command.Parameters.Add(new SqlParameter("@Caption", Caption));
    //		command.Parameters.Add(new SqlParameter("@BytesOriginal", BytesOriginal));
    //		command.Parameters.Add(new SqlParameter("@BytesFull", ResizeImageFile(BytesOriginal, 600)));
    //		command.Parameters.Add(new SqlParameter("@BytesPoster", ResizeImageFile(BytesOriginal, 198)));
    //		command.Parameters.Add(new SqlParameter("@BytesThumb", ResizeImageFile(BytesOriginal, 100)));
    		connection.Open();
    		command.ExecuteNonQuery();
                    int PhotoID = (int) command.Parameters["@ReturnValue"].Value;
                    string path = HttpContext.Current.Server.MapPath("~/Albums/Album_" + AlbumID.ToString() + "/" + PhotoID.ToString() + ".jpg");
                    FileStream f = new FileStream(path, FileMode.Create);
                    f.Write(BytesOriginal, 0, BytesOriginal.Length);
                    f.Close();
                    AddThumbnail(AlbumID, PhotoID, BytesOriginal);
                }
    		}
    
    I use the database for ordering and grouping. I also added a @Returnvalue. The stored procedure will return the @@IDENTITY of the new record. With that i can assemble a path. For me it was an Album+Number as a subdirectory in "albums". The returnvalue is the ID of the file. The addThumbnail function is:
        public static void AddThumbnail(int AlbumID, int PhotoID, byte[] BytesOriginal) {
            string path = HttpContext.Current.Server.MapPath("~/Albums/Album_" + AlbumID.ToString() + "/ThumbNails/" + PhotoID.ToString() + ".jpg");
            FileStream f = new FileStream(path, FileMode.Create);
            byte[] ThumbNail = ResizeImageFile(BytesOriginal, 100);
            f.Write(ThumbNail, 0, ThumbNail.Length);
            f.Close();
        }
    
    I hope this helps.
    Tuesday, March 14, 2006 4:12 PM
  • User1923026465 posted

    This is not working for me.  No one out there has converted the club site to store images outside of the DB?  If not does anyone have a tutorial on how someone would do this from scratch?  I want to know how to upload a picture to a folder while posting information as to the location of the file in the DB.  How to write the query and how to code it to pull the location form the DB and present the image on a page from a query?  Make sense.  This has been done forever, I just am new to development and I am trying to figure this out with no luck...  Any words of wisdom out there?<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p>

    Friday, March 17, 2006 10:40 AM
  • User506323975 posted

    Did you try the code i posted? It will store the picture in a file on disk. You do have to give the IISuser permission to create files and subdirectories. It should work fine with only a little code change compared to the original.

    Some changes have to be made to the stored procedure. You can see in the code which parameters are not used anymore. Those have to be removed from the stored procedure parameter list.

    At the end of the stored procedure you have to return the last identity of the newly created record.

    You can do that with

    return @@IDENTITY

    If you need help with this, just ask. But please be very specific on what part. Sometimes best to do things step by step.

     

    Friday, March 17, 2006 4:58 PM
  • User1275632693 posted

    How about also adding a way to have the Photo's be pre-rendered so you dont have to refresh the enitre page each time the next button is clicked.

     

    Friday, March 17, 2006 8:18 PM
  • User1923026465 posted

    Ok, I have reviewed this code, and I assume it was written in C#.  I converted it to VB, which is what I am using and I am trying to make sense of it.  The club site is slightly different an that is why I think I am confused.  The club site does not use store procedures as a part of the uploading of images.  From what I can see everything is in the ImageHandling.vb file (as it relates to uploading and storing the file).  Below is the code.  What lines to I begin to look at changing to save the image to the file folder I specify and store the location in the location in the DB.  Thanks for the help...

    Imports Microsoft.VisualBasic<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p>

    Imports System.Data.SqlClient<o:p></o:p>

    <o:p> </o:p>

    Public Class ImageUtils<o:p></o:p>

    <o:p> </o:p>

        'Todo: Change this to use a database query through the middle tier<o:p></o:p>

    <o:p> </o:p>

        Public Shared Function uploadImage(ByVal title As String, ByVal albumid As Integer, ByVal data As IO.Stream) As Integer<o:p></o:p>

            Dim origImageData(CInt(data.Length - 1)) As Byte<o:p></o:p>

            data.Read(origImageData, 0, CInt(data.Length))<o:p></o:p>

    <o:p> </o:p>

            Dim connection As New SqlConnection(ConfigurationManager.ConnectionStrings("ClubSiteDB").ConnectionString)<o:p></o:p>

            Dim command As New SqlCommand("INSERT INTO Images (title, origimage, largeimage, thumbimage, album) VALUES ( @title, @origimage, @largeimage, @thumbimage, @albumid); select SCOPE_IDENTITY()", connection)<o:p></o:p>

    <o:p> </o:p>

            Dim param0 As New SqlParameter("@title", System.Data.SqlDbType.VarChar, 50)<o:p></o:p>

            param0.Value = title<o:p></o:p>

            command.Parameters.Add(param0)<o:p></o:p>

    <o:p> </o:p>

            Dim param1 As New SqlParameter("@origimage", System.Data.SqlDbType.Image)<o:p></o:p>

            param1.Value = origImageData<o:p></o:p>

            command.Parameters.Add(param1)<o:p></o:p>

    <o:p> </o:p>

            Dim param2 As New SqlParameter("@largeimage", System.Data.SqlDbType.Image)<o:p></o:p>

            param2.Value = MakeThumb(origImageData, 350)<o:p></o:p>

            command.Parameters.Add(param2)<o:p></o:p>

    <o:p> </o:p>

            Dim param3 As New SqlParameter("@thumbimage", System.Data.SqlDbType.Image)<o:p></o:p>

            param3.Value = MakeThumb(origImageData, 69, 69)<o:p></o:p>

            command.Parameters.Add(param3)<o:p></o:p>

    <o:p> </o:p>

            Dim param4 As New SqlParameter("@albumid", System.Data.SqlDbType.Int)<o:p></o:p>

            param4.Value = albumid<o:p></o:p>

            command.Parameters.Add(param4)<o:p></o:p>

    <o:p> </o:p>

            connection.Open()<o:p></o:p>

    <o:p> </o:p>

            Dim result As Object = command.ExecuteScalar()<o:p></o:p>

            connection.Close()<o:p></o:p>

    <o:p> </o:p>

            If Not result Is Nothing Then<o:p></o:p>

                Return CInt(result)<o:p></o:p>

            Else<o:p></o:p>

                Return 0<o:p></o:p>

            End If<o:p></o:p>

        End Function<o:p></o:p>

    <o:p> </o:p>

    <o:p> </o:p>

        Const sizeThumb As Integer = 69<o:p></o:p>

    <o:p> </o:p>

        Public Shared Function MakeThumb(ByVal fullsize As Byte()) As Byte()<o:p></o:p>

            Dim iOriginal, iThumb As System.Drawing.Image<o:p></o:p>

            Dim targetH, targetW As Integer<o:p></o:p>

    <o:p> </o:p>

            ' Grab Original Image<o:p></o:p>

            iOriginal = System.Drawing.Image.FromStream(New IO.MemoryStream(fullsize))<o:p></o:p>

            ' Find Height and Width for Thumbnail Image<o:p></o:p>

            If (iOriginal.Height > iOriginal.Width) Then<o:p></o:p>

                targetH = sizeThumb<o:p></o:p>

                targetW = CInt(iOriginal.Width * (sizeThumb / iOriginal.Height))<o:p></o:p>

            Else<o:p></o:p>

                targetW = sizeThumb<o:p></o:p>

                targetH = CInt(iOriginal.Height * (sizeThumb / iOriginal.Width))<o:p></o:p>

            End If<o:p></o:p>

            iThumb = iOriginal.GetThumbnailImage(targetW, targetH, Nothing, System.IntPtr.Zero)<o:p></o:p>

            Dim m As New IO.MemoryStream()<o:p></o:p>

            iThumb.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg)<o:p></o:p>

            Return m.GetBuffer()<o:p></o:p>

        End Function<o:p></o:p>

    <o:p> </o:p>

    <o:p> </o:p>

        Public Shared Function MakeThumb(ByVal fullsize As Byte(), ByVal newwidth As Integer, ByVal newheight As Integer) As Byte()<o:p></o:p>

            Dim iOriginal, iThumb As System.Drawing.Image<o:p></o:p>

            Dim scaleH, scaleW As Double<o:p></o:p>

            Dim srcRect As Drawing.Rectangle<o:p></o:p>

    <o:p> </o:p>

    <o:p> </o:p>

            ' Grab Original Image<o:p></o:p>

            iOriginal = System.Drawing.Image.FromStream(New IO.MemoryStream(fullsize))<o:p></o:p>

            ' Find Height and Width for Thumbnail Image<o:p></o:p>

    <o:p> </o:p>

            scaleH = iOriginal.Height / newheight<o:p></o:p>

            scaleW = iOriginal.Width / newwidth<o:p></o:p>

            If scaleH = scaleW Then<o:p></o:p>

                srcRect.Width = iOriginal.Width<o:p></o:p>

                srcRect.Height = iOriginal.Height<o:p></o:p>

                srcRect.X = 0<o:p></o:p>

                srcRect.Y = 0<o:p></o:p>

            ElseIf (scaleH) > (scaleW) Then<o:p></o:p>

                srcRect.Width = iOriginal.Width<o:p></o:p>

                srcRect.Height = CInt(newheight * scaleW)<o:p></o:p>

                srcRect.X = 0<o:p></o:p>

                srcRect.Y = CInt((iOriginal.Height - srcRect.Height) / 2)<o:p></o:p>

            Else<o:p></o:p>

                srcRect.Width = CInt(newwidth * scaleH)<o:p></o:p>

                srcRect.Height = iOriginal.Height<o:p></o:p>

                srcRect.X = CInt((iOriginal.Width - srcRect.Width) / 2)<o:p></o:p>

                srcRect.Y = 0<o:p></o:p>

            End If<o:p></o:p>

    <o:p> </o:p>

            iThumb = New System.Drawing.Bitmap(newwidth, newheight)<o:p></o:p>

            Dim g As Drawing.Graphics = Drawing.Graphics.FromImage(iThumb)<o:p></o:p>

            g.DrawImage(iOriginal, New Drawing.Rectangle(0, 0, newwidth, newheight), srcRect, Drawing.GraphicsUnit.Pixel)<o:p></o:p>

    <o:p> </o:p>

            Dim m As New IO.MemoryStream()<o:p></o:p>

            iThumb.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg)<o:p></o:p>

            Return m.GetBuffer()<o:p></o:p>

        End Function<o:p></o:p>

    <o:p> </o:p>

        Public Shared Function MakeThumb(ByVal fullsize As Byte(), ByVal maxwidth As Integer) As Byte()<o:p></o:p>

            Dim iOriginal, iThumb As System.Drawing.Image<o:p></o:p>

            Dim scale As Double<o:p></o:p>

    <o:p> </o:p>

            ' Grab Original Image<o:p></o:p>

            iOriginal = System.Drawing.Image.FromStream(New IO.MemoryStream(fullsize))<o:p></o:p>

    <o:p> </o:p>

            If iOriginal.Width > maxwidth Then<o:p></o:p>

    <o:p> </o:p>

                scale = iOriginal.Width / maxwidth<o:p></o:p>

                Dim newheight As Integer = CInt(iOriginal.Height / scale)<o:p></o:p>

    <o:p> </o:p>

                iThumb = New System.Drawing.Bitmap(iOriginal, maxwidth, newheight)<o:p></o:p>

                Dim m As New IO.MemoryStream()<o:p></o:p>

                iThumb.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg)<o:p></o:p>

                Return m.GetBuffer()<o:p></o:p>

            Else<o:p></o:p>

                Return fullsize<o:p></o:p>

            End If<o:p></o:p>

        End Function<o:p></o:p>

    <o:p> </o:p>

    End Class

     

    Monday, March 27, 2006 4:04 PM
  • User1923026465 posted

    Bump...

    Anyone have any ideas?

    Wednesday, March 29, 2006 6:41 AM
  • User1923026465 posted

    Well I can't cross post so I have to keep bumping this string until I get an answer.  I am not sure what I need to change to get the club site to save photos to the file system rather than to the db.  I know how do it in simplistic terms.  I did it in a page I added on to list supported links and have it where I can upload their logo to the file system and retrive it from on a different page.  This is the sub I used to do it;

     

        Protected Sub FormView1_ItemUpdated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.FormViewUpdatedEventArgs)
            Response.Redirect("links_list.aspx")
        End Sub
        
        Protected Sub FormView1_ItemInserted(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.FormViewInsertedEventArgs)
            Dim FileUpload1 As FileUpload = CType(FormView1.FindControl("FileUpload1"), FileUpload)
            Dim nameTextBox As TextBox = CType(FormView1.FindControl("nameTextBox"), TextBox)
            Dim webTextBox As TextBox = CType(FormView1.FindControl("webTextBox"), TextBox)
            Dim descTextBox As TextBox = CType(FormView1.FindControl("descTextBox"), TextBox)
            FileUpload1.SaveAs(Server.MapPath("~\uploads\photos\" & FileUpload1.FileName.ToString()))
            SqlDataSource1.InsertParameters.Add("logo", FileUpload1.FileName.ToString())
            SqlDataSource1.InsertParameters.Add("name", nameTextBox.Text.ToString())
            SqlDataSource1.InsertParameters.Add("website", webTextBox.Text.ToString())
            SqlDataSource1.InsertParameters.Add("description", descTextBox.Text.ToString())
            SqlDataSource1.InsertCommand = "INSERT INTO Links(logo, name, website, description) VALUES (@logo, @name, @website, @description)"
            SqlDataSource1.Insert()
            Response.Redirect("links_list.aspx")
        End Sub

     What throws me of in the code for the clubsite is that it is complicated with the routines that create the thumbnails.  I need help trying to figure this out.  Any advise?

    Tuesday, April 11, 2006 8:51 AM
  • User654902800 posted

    Hiya Shane, it's not something you can fix by changing the code in just one page, maybe that's why no answers so far. This is really crude, but should give you an idea - 

    'instead of putting images in db, put null in for param1,2&3
            param3.Value = DBNull
    'then after the database insert is done, you get back the return value which is going to be your filename

    Dim result As Object = command.ExecuteScalar()
    connection.Close()
    <?XML:NAMESPACE PREFIX = O /><O:P>Dim filename As String
    </O:P>
    If Not result Is Nothing Then
                filename=result.ToString()

             'now get the images into the file system
             'the original is already in a byte array, get the other two

                Dim foo as Byte() = MakeThumb(origImageData, 69, 69)
                Dim bar as Byte() = MakeThumb(origImageData, 350)

                'now pass them to a method to save to disk which you can write

                SaveFile(foo, filename & ".thumb.jpg")
                SaveFile(bar, filename & ".large.jpg")
                SaveFile(origImageData, filename & ".full.jpg")
                Return CInt(result)
    Else ' some db error
                Return 0
    End If<O:P></O:P>

    The savefile sub you should find easy, basically like Khun Jean said earlier in the thread

    public shared sub savefile(BytesOriginal as byte(), filename as string)
                    dim path as string = HttpContext.Current.Server.MapPath("~/somefolder/" & filename);
                    dim f as Filestream = new FileStream(path, FileMode.Create);
                    f.Write(BytesOriginal, 0, BytesOriginal.Length);
                    f.Close();
    end sub

    Tuesday, April 11, 2006 1:28 PM
  • User1923026465 posted
    Thanks, this all makes sense.  I have tried putting it together.  I currently have one question.  When I try to declare each of the parameters (param3.Value = DBNull), I get error;  'DBnull' is a type and not an expression.  Am I suppose to use DBnull or something else like Nothing?
    Tuesday, April 11, 2006 4:22 PM
  • User654902800 posted

    My bad, I just tossed DBNull in there. Inserting a null into the db wasn't going to fly anyhow since the origimage column in the db doesn't allow nulls. A more tidy way to go would be this...

    first you need to go to server explorer, to the images table in the club db, right click and open table definition. Change the origimage column to allow nulls (or if you're really wanting to toss the images in the db, you could drop the 3 image columns completely, either way works with the following code).

    Then change the insert part of the code to

            Dim command As New SqlCommand("INSERT INTO Images (title, album) VALUES ( @title, @albumid); select SCOPE_IDENTITY()", connection)

            Dim param0 As New SqlParameter("@title", System.Data.SqlDbType.VarChar, 50)
            param0.Value = title
            command.Parameters.Add(param0)

            Dim param1 As New SqlParameter("@albumid", System.Data.SqlDbType.Int)
            param1.Value = albumid
            command.Parameters.Add(param1)

    Basically just get rid of those 3 image params altogether.

    Sorry about that.

    Tuesday, April 11, 2006 4:46 PM
  • User1923026465 posted
    MrLunch, you have been a great help.  Works like a charm.  Now that I have the upload complete, what needs to be done to the imagefetch.ashx file to know what images to pull?
    Wednesday, April 12, 2006 9:59 AM
  • User1275632693 posted

    Excellent thread, I am still debating if I really want to remove the images from the DB. I suppose in my case it is not as critical as I am on my own server and not so worried about size limitations.

    How about preloading the images into a control so when you are clicking next_image you do not need to refresh the entire page.

     

    Wednesday, April 12, 2006 10:58 AM
  • User1923026465 posted
    I am open to anything, I just need to figure out how to do something.  I am still trying to build my knowledge, so I am limited to my current abilities...
    Wednesday, April 12, 2006 11:02 AM
  • User654902800 posted

    MrLunch, you have been a great help.  Works like a charm.  Now that I have the upload complete, what needs to be done to the imagefetch.ashx file to know what images to pull?

    You've got a few rhings to deal with besides reading rhe file off disk - like deleting the disk files when you hit that 'remove image' button. That should be pretty easy to figure out.
    Here's how to send the file from disk - TransmitFile() rocks, you didn't want to stream a byte array anyhow :)
    Note I didn't test this code! Also note there's no error handling (sorely missing from the starter kit overall).

        Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
            Dim response As Web.HttpResponse = context.Response
            Dim request As Web.HttpRequest = context.Request
            Dim filename As String
            ' cast from string to int to string here to prevent
            ' hack, since we are going to use 'id' as part of a filename
            Dim id As String = CInt(request.QueryString("ImageID")).ToString()
            Dim size As Integer = CInt(request.QueryString("Size"))
            'static so we don't have to lookup each time
            Static path As String
           
            If String.IsNullOrEmpty(path) Then
                path = HttpContext.Current.Server.MapPath("~/somefolder/")
            EndIf
           
            filename = path
           
            ' figure out the filename
            Select Case size
                Case 0 : filename += id & ".large.jpg"
                Case 1 : filename += id & ".thumb.jpg"
                Case 2 : filename += id & ".full.jpg"
                Case Else : filename += id & ".large.jpg"
            End Select
           
            ' and blammo
            response.ContentType = "image/jpeg"
            response.Cache.SetCacheability(HttpCacheability.Public)
            response.TransmitFile(filename)
            response.End()
           
        End Sub


    All that said, I would keep the images in the database for a couple of reasons. Mainly, image files on disc won't scale to multiple web servers, and even if you don't see that happening in your future, your hosting provider might do it, and it's good practice to code for keeps, eh?
    If raw speed is the goal, and you don't have a machine with enough memory to cache all the images, then you could leave the images in the db and cache them on disk, like this psuedo-code

    request comes in for image 6
    try{
       transmitfile(6)
    }
    catch (notfound) {
       get image 6 from db and write to disk
       write image out
    }

    when image is deleted from db, also delete any disk cached files

    That uses twice the disk space but reduces the db hits and makes the image delivery fast and works for multiple servers too.

    Wednesday, April 12, 2006 1:54 PM
  • User654902800 posted

    How about preloading the images into a control so when you are clicking next_image you do not need to refresh the entire page.

    I did that with Atlas instead - on a bunch of the places where there is a prev-next or Page 1 2 3 - so that only the part of the page that changes is refreshed. It's drop-dead easy.

    I could never get a grip on that preload stuff - javascript makes me want to go wash my hands all the time. :)

    Wednesday, April 12, 2006 1:59 PM
  • User654902800 posted

    As long as we are looking at the imagefetch code - it jumped out at me there's a security problem with it (either version, disk or db). You can point a browser to imagefetch and get any image that is there, regardless of whether it's in a private album or not. So those private albums are really only private on the honor system. Changing 'private' to 'not visible in public list of albums' would be more accurate.

    Well, if all the work were already done, what would we do for entertainment? ;-)

    Wednesday, April 12, 2006 2:30 PM
  • User1923026465 posted
    MrLunch, I tip my hat to you.  It worked perfectly.  Everything is working as expected.  Do I need to worry about any other files to your knowledge?  The file I have in mind is the ImageThumbnail.ascx...
    Wednesday, April 12, 2006 4:04 PM
  • User1923026465 posted

    I understand the benefits of leaving the images in the database however, the host that this site it at has a limitation of 10mb for the database, however I have 50GB of space on the file system.  I am not too concerned about security; everything will be for public viewing.  The only membership limitation I have at this point it the ability to manage content.  Thanks for all your help.  It is greatly appreciated.

    Wednesday, April 12, 2006 4:10 PM
  • User654902800 posted

    MrLunch, I tip my hat to you.  It worked perfectly.  Everything is working as expected.  Do I need to worry about any other files to your knowledge?  The file I have in mind is the ImageThumbnail.ascx...

    Thanks for the hat tip, that's what it's all about. :)

    ImageThumbnail.ascx uses ImageFetch.ashx as the url, so no worries there. I didn't look elsewhere for any related code, but I think you have it covered, except for deleting images, which I think you should be able to handle when and if you decide to delete one :)

    the host that this site it at has a limitation of 10mb for the database, however I have 50GB of space on the file system...

    Well then it makes good sense what you're doing.

    Cheers.

     

    Wednesday, April 12, 2006 8:22 PM
  • User1275632693 posted

    Hi Guys,

    I am tempted to switch it though as it would make it easier to display databound images from the DB as the gridview and datagrid are using imageurl as opposed to image object.

    I will have some fun with this down the road for now its back to the drawing board.

     

     

    Wednesday, April 12, 2006 9:08 PM
  • User-1816507503 posted

    Man, you guys really seem to have your act together! Great job! That's one thing that I love about these forums, everyone's so willing to help. Hackneys...is there anyway you can post the steps necessary to make this conversion from DB to file storage? Alot of us out here, like myself, aren't exactly DB oriented and this can prove to be a huge challenge, while all the while we're reinventing the wheel since you've already got this licked.

    Any help is greatly appreciated and TIA!

    Sunday, April 16, 2006 11:50 AM
  • User654902800 posted

    OK, here's the whole procedure....

    First, make a new folder in the root of the web site called 'photos', right click the root node in Server Explorer, then Add Folder...
    (if you want to call it something else, you'll need to change it in the code below in 2 places).

    Next, alter the database so it won't mind that you're not going to be stuffing images in it anymore.
    This change will make it so you can go back to using the db if you change your mind...
    Go to Server Explorer, find the Club.mdf, expand the tree until you can right click on the images table and select Open Table Definition.
    Check the Allow Nulls box for all of largeimage, thumbimage and origimage.
    Save that, and close it, and then right click club.mdf in Server Explorer and select Detach Database.

    What you just did was make it so you can insert stuff into that table without having to insert any images - allows nulls, makes sense eh?. If you decide to go back to using the db, you don't have to change this back.
    You could also just delete those 3 columns (later on you might want to add some new ones, like the date a file was uploaded and the id of the user who did it and so on).

    Now you just need to change the code in 2 files - ImageFetch.ascx and ImageHandling.vb

    ----------------------------
    Here's the new code for ImageHandling.vb
    ----------------------------

    Imports Microsoft.VisualBasic
    Imports System.Data.SqlClient

    Public Class ImageUtils

        ''' <summary>
        ''' Saves a photo - meta info to database and image files to disk
        ''' </summary>
        ''' <param name="title"></param>
        ''' <param name="albumid"></param>
        ''' <param name="data"></param>
        ''' <returns></returns>
        ''' <remarks>Assumes a folder in the website root called 'photos'</remarks>
        Public Shared Function uploadImage(ByVal title As String, ByVal albumid As Integer, ByVal data As IO.Stream) As Integer
            Dim origImageData(CInt(data.Length - 1)) As Byte
            data.Read(origImageData, 0, CInt(data.Length))

            Dim connection As New SqlConnection(ConfigurationManager.ConnectionStrings("ClubSiteDB").ConnectionString)
            Dim command As New SqlCommand("INSERT INTO Images (title, album) VALUES ( @title, @albumid); select SCOPE_IDENTITY()", connection)

            Dim param0 As New SqlParameter("@title", System.Data.SqlDbType.VarChar, 50)
            param0.Value = title
            command.Parameters.Add(param0)

            Dim param1 As New SqlParameter("@albumid", System.Data.SqlDbType.Int)
            param1.Value = albumid
            command.Parameters.Add(param1)

            connection.Open()

            Dim result As Object = command.ExecuteScalar()
            connection.Close()
            Dim filename As String
            If Not result Is Nothing Then
                filename = result.ToString()

                'now get the images into the file system
                'the original is already in a byte array
                SaveImageFile(origImageData, filename & ".full.jpg")
                'get the other two
                Dim foo As Byte() = MakeThumb(origImageData. 69, 69)
                SaveImageFile(foo, filename & ".thumb.jpg")
                Dim bar As Byte() = MakeThumb(origImageData, 350)
                SaveImageFile(bar, filename & ".large.jpg")

                Return CInt(result)
            Else ' some db error
                Return 0
            End If

        End Function

        Public Shared Sub SaveImageFile(ByVal BytesOriginal As Byte(), ByVal filename As String)
            Dim path As String = HttpContext.Current.Server.MapPath("~/photos/" & filename)
            Dim f As Filestream = New FileStream(path, FileMode.Create)
            f.Write(BytesOriginal, 0, BytesOriginal.Length)
            f.Close()
        End Sub

        Const sizeThumb As Integer = 69

        ''' <summary>
        ''' makes a properly scaled thumb with sizeThumb as max dimension
        ''' </summary>
        ''' <param name="fullsize"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function MakeThumb(ByVal fullsize As Byte()) As Byte()
            Dim iOriginal, iThumb As System.Drawing.Image
            Dim targetH, targetW As Integer

            ' Grab Original Image
            iOriginal = System.Drawing.Image.FromStream(New IO.MemoryStream(fullsize))
            ' Find Height and Width for Thumbnail Image
            If iOriginal.Height < sizeThumb And iOriginal.Width < sizeThumb Then
                Return fullsize
            End If

            If (iOriginal.Height > iOriginal.Width) Then
                targetH = sizeThumb
                targetW = CInt(iOriginal.Width * (sizeThumb / iOriginal.Height))
            Else
                targetW = sizeThumb
                targetH = CInt(iOriginal.Height * (sizeThumb / iOriginal.Width))
            End If
            iThumb = iOriginal.GetThumbnailImage(targetW, targetH, Nothing, System.IntPtr.Zero)
            Dim m As New IO.MemoryStream()
            iThumb.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg)
            Return m.GetBuffer()
        End Function


        Public Shared Function MakeThumb(ByVal fullsize As Byte(), ByVal newwidth As Integer, ByVal newheight As Integer) As Byte()
            Dim iOriginal, iThumb As System.Drawing.Image
            Dim scaleH, scaleW As Double
            Dim srcRect As Drawing.Rectangle


            ' Grab Original Image
            iOriginal = System.Drawing.Image.FromStream(New IO.MemoryStream(fullsize))
            ' Find Height and Width for Thumbnail Image

            scaleH = iOriginal.Height / newheight
            scaleW = iOriginal.Width / newwidth
            If scaleH = scaleW Then
                srcRect.Width = iOriginal.Width
                srcRect.Height = iOriginal.Height
                srcRect.X = 0
                srcRect.Y = 0
            ElseIf (scaleH) > (scaleW) Then
                srcRect.Width = iOriginal.Width
                srcRect.Height = CInt(newheight * scaleW)
                srcRect.X = 0
                srcRect.Y = CInt((iOriginal.Height - srcRect.Height) / 2)
            Else
                srcRect.Width = CInt(newwidth * scaleH)
                srcRect.Height = iOriginal.Height
                srcRect.X = CInt((iOriginal.Width - srcRect.Width) / 2)
                srcRect.Y = 0
            End If

            iThumb = New System.Drawing.Bitmap(newwidth, newheight)
            Dim g As Drawing.Graphics = Drawing.Graphics.FromImage(iThumb)
            g.DrawImage(iOriginal, New Drawing.Rectangle(0, 0, newwidth, newheight), srcRect, Drawing.GraphicsUnit.Pixel)

            Dim m As New IO.MemoryStream()
            iThumb.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg)
            Return m.GetBuffer()
        End Function

        Public Shared Function MakeThumb(ByVal fullsize As Byte(), ByVal maxwidth As Integer) As Byte()
            Dim iOriginal, iThumb As System.Drawing.Image
            Dim scale As Double

            ' Grab Original Image
            iOriginal = System.Drawing.Image.FromStream(New IO.MemoryStream(fullsize))

            If iOriginal.Width > maxwidth Then

                scale = iOriginal.Width / maxwidth
                Dim newheight As Integer = CInt(iOriginal.Height / scale)

                iThumb = New System.Drawing.Bitmap(iOriginal, maxwidth, newheight)
                Dim m As New IO.MemoryStream()
                iThumb.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg)
                Return m.GetBuffer()
            Else
                Return fullsize
            End If
        End Function

    End Class

    ------------------------------
    Here's the new code for ImageFetch.ascx
    ------------------------------

    <%@ WebHandler Language="VB" Class="ImageFetch" %>

    Imports System.Web
    Imports System.Data.SqlClient
    Imports System.Data

    'The idea behind this is to simply fetch the image off the disk and slam it down the network.
    Public Class ImageFetch : Implements IHttpHandler

        Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
            Get
                Return True
            End Get
        End Property

        Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
            Dim response As Web.HttpResponse = context.Response
            Dim request As Web.HttpRequest = context.Request
            Dim filename As String
            Dim size As Integer = CInt(request.QueryString("Size"))
            ' cast from string to int to string here to prevent
            ' hack, since we are going to use 'id' as part of a filename
            Dim id As String = CInt(request.QueryString("ImageID")).ToString()

            'static so we don't have to lookup each time
            'save several milliseconds/year :)
            Static path As String
            If String.IsNullOrEmpty(path) Then
                path = HttpContext.Current.Server.MapPath("~/photos/")
            End If

            filename = path

            ' figure out the filename
            Select Case size
                Case 0 : filename += id & ".large.jpg"
                Case 1 : filename += id & ".thumb.jpg"
                Case 2 : filename += id & ".full.jpg"
                Case Else : filename += id & ".large.jpg"
            End Select

            ' and blammo
            response.ContentType = "image/jpeg"
            response.Cache.SetCacheability(HttpCacheability.Public)
            response.TransmitFile(filename)
            response.End()

        End Sub
    End Class

    I hope there aren't any typos in there :) Let me know how it goes for you.

    Cheers.

    Tuesday, April 18, 2006 3:15 PM
  • User-1816507503 posted

    Holy Cow Mr. Lunch! YOU ROCK!!!! The ImageHandling.vb had a few typos in it, but they were easy to fix. I've included the corrected ImageHandling.vb. Thanks again SOOO much for your help. It works like a charm!

     

    New ImageHandling.vb

    ---------------------------------------------

    Imports Microsoft.VisualBasic

    Imports System.Data.SqlClient

    Public Class ImageUtils

    ''' <summary>

    ''' Saves a photo - meta info to database and image files to disk

    ''' </summary>

    ''' <param name="title"></param>

    ''' <param name="albumid"></param>

    ''' <param name="data"></param>

    ''' <returns></returns>

    ''' <remarks>Assumes a folder in the website root called 'photos'</remarks>

    Public Shared Function uploadImage(ByVal title As String, ByVal albumid As Integer, ByVal data As IO.Stream) As Integer

    Dim origImageData(CInt(data.Length - 1)) As Byte

    data.Read(origImageData, 0, CInt(data.Length))

    Dim connection As New SqlConnection(ConfigurationManager.ConnectionStrings("ClubSiteDB").ConnectionString)

    Dim command As New SqlCommand("INSERT INTO Images (title, album) VALUES ( @title, @albumid); select SCOPE_IDENTITY()", connection)

    Dim param0 As New SqlParameter("@title", System.Data.SqlDbType.VarChar, 50)

    param0.Value = title

    command.Parameters.Add(param0)

    Dim param1 As New SqlParameter("@albumid", System.Data.SqlDbType.Int)

    param1.Value = albumid

    command.Parameters.Add(param1)

    connection.Open()

    Dim result As Object = command.ExecuteScalar()

    connection.Close()

    Dim filename As String

    If Not result Is Nothing Then

    filename = result.ToString()

    'now get the images into the file system

    'the original is already in a byte array

    SaveImageFile(origImageData, filename & ".full.jpg")

    'get the other two

    Dim foo As Byte() = MakeThumb(origImageData, 69, 69)

    SaveImageFile(foo, filename & ".thumb.jpg")

    Dim bar As Byte() = MakeThumb(origImageData, 350)

    SaveImageFile(bar, filename & ".large.jpg")

    Return CInt(result)

    Else ' some db error

    Return 0

    End If

    End Function

    Public Shared Sub SaveImageFile(ByVal BytesOriginal As Byte(), ByVal filename As String)

    Dim path As String = HttpContext.Current.Server.MapPath("~/photos/" & filename)

    Dim f As IO.FileStream = New IO.FileStream(path, IO.FileMode.Create)

    f.Write(BytesOriginal, 0, BytesOriginal.Length)

    f.Close()

    End Sub

    Const sizeThumb As Integer = 69

    ''' <summary>

    ''' makes a properly scaled thumb with sizeThumb as max dimension

    ''' </summary>

    ''' <param name="fullsize"></param>

    ''' <returns></returns>

    ''' <remarks></remarks>

    Public Shared Function MakeThumb(ByVal fullsize As Byte()) As Byte()

    Dim iOriginal, iThumb As System.Drawing.Image

    Dim targetH, targetW As Integer

    ' Grab Original Image

    iOriginal = System.Drawing.Image.FromStream(New IO.MemoryStream(fullsize))

    ' Find Height and Width for Thumbnail Image

    If iOriginal.Height < sizeThumb And iOriginal.Width < sizeThumb Then

    Return fullsize

    End If

    If (iOriginal.Height > iOriginal.Width) Then

    targetH = sizeThumb

    targetW = CInt(iOriginal.Width * (sizeThumb / iOriginal.Height))

    Else

    targetW = sizeThumb

    targetH = CInt(iOriginal.Height * (sizeThumb / iOriginal.Width))

    End If

    iThumb = iOriginal.GetThumbnailImage(targetW, targetH, Nothing, System.IntPtr.Zero)

    Dim m As New IO.MemoryStream()

    iThumb.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg)

    Return m.GetBuffer()

    End Function

    Public Shared Function MakeThumb(ByVal fullsize As Byte(), ByVal newwidth As Integer, ByVal newheight As Integer) As Byte()

    Dim iOriginal, iThumb As System.Drawing.Image

    Dim scaleH, scaleW As Double

    Dim srcRect As Drawing.Rectangle

    ' Grab Original Image

    iOriginal = System.Drawing.Image.FromStream(New IO.MemoryStream(fullsize))

    ' Find Height and Width for Thumbnail Image

    scaleH = iOriginal.Height / newheight

    scaleW = iOriginal.Width / newwidth

    If scaleH = scaleW Then

    srcRect.Width = iOriginal.Width

    srcRect.Height = iOriginal.Height

    srcRect.X = 0

    srcRect.Y = 0

    ElseIf (scaleH) > (scaleW) Then

    srcRect.Width = iOriginal.Width

    srcRect.Height = CInt(newheight * scaleW)

    srcRect.X = 0

    srcRect.Y = CInt((iOriginal.Height - srcRect.Height) / 2)

    Else

    srcRect.Width = CInt(newwidth * scaleH)

    srcRect.Height = iOriginal.Height

    srcRect.X = CInt((iOriginal.Width - srcRect.Width) / 2)

    srcRect.Y = 0

    End If

    iThumb = New System.Drawing.Bitmap(newwidth, newheight)

    Dim g As Drawing.Graphics = Drawing.Graphics.FromImage(iThumb)

    g.DrawImage(iOriginal, New Drawing.Rectangle(0, 0, newwidth, newheight), srcRect, Drawing.GraphicsUnit.Pixel)

    Dim m As New IO.MemoryStream()

    iThumb.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg)

    Return m.GetBuffer()

    End Function

    Public Shared Function MakeThumb(ByVal fullsize As Byte(), ByVal maxwidth As Integer) As Byte()

    Dim iOriginal, iThumb As System.Drawing.Image

    Dim scale As Double

    ' Grab Original Image

    iOriginal = System.Drawing.Image.FromStream(New IO.MemoryStream(fullsize))

    If iOriginal.Width > maxwidth Then

    scale = iOriginal.Width / maxwidth

    Dim newheight As Integer = CInt(iOriginal.Height / scale)

    iThumb = New System.Drawing.Bitmap(iOriginal, maxwidth, newheight)

    Dim m As New IO.MemoryStream()

    iThumb.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg)

    Return m.GetBuffer()

    Else

    Return fullsize

    End If

    End Function

    End Class

    ------------------------------------------------

     

    Ok I can't say it enough so one more time. Thanks again for your help Mr. Lunch!

    Tuesday, April 18, 2006 9:17 PM
  • User1275632693 posted

    Now this is what I call a good sense of community.

    Keep on CSK'ng It !

     

     :)

     

    Tuesday, April 18, 2006 9:41 PM
  • User1869269768 posted
    Now you just need to change the code in 2 files - ImageFetch.ascx and ImageHandling.vb


    Cool but in Personal Starter Kit I can't find the files ImageFetch.ascx and ImageHandling.vb. What are their equivalnets in Personal Starter Kit and how should I change them?
    Tuesday, May 2, 2006 11:00 AM
  • User-680927624 posted

    This code works great locally, but when I implement it on a different host (godaddy.com) I get an access denied error when it attempts to upload. Anyone know a workaround?

    Thanks...

    Monday, May 22, 2006 7:07 PM
  • User654902800 posted
    You'll need to figure out how to set access controls on the photos folder such that the aspnet user (or whatever user the site runs under) has read/write in that folder. If GoDaddy supports it, it'll be in the FAQ someplace.
    Monday, May 22, 2006 11:21 PM
  • User-1653776603 posted

    This is why we originally wrote the starter kit to use the database, your chances of being able to write to it are much higher than the filesystem.

    Some other notes for the change to use the filesystem:

    • The code you posted could be optimized by passing the filename into make thumb, and having it write the file directly to the filesystem. Right now, it writes to a memory stream, and then saves that to disk. Depending on how the image is written, this could save a bit of memory use.
    • Good catch on the ability to pass arbitrary imageid's to image fetch, even if the album is private. A fix would be to check the permissions on the album as part of the image fetch code, but that would require a sql lookup on the fetch of each image.
    • If you can make the path where the images are stored directly accessible by URL, then you could skip using imagefetch.ashx all together, and use the url directly to the image. I think that all the images are exposed by the imagethumbnail control anyway, so if you want to change that kind of lookup, it could be done in the code for that control, and would then apply to all the places images are shown.
    • Avatars for members are stored in a seperate table, and they could also be persisted in the file system, but as they are likely to be much smaller, its probably not that important.

     Overall its great to see that people are extending the kit, and getting some use out of it.

    Sam

    Thursday, May 25, 2006 5:19 PM
  • User-680927624 posted

    This code works great locally, but when I implement it on a different host (godaddy.com) I get an access denied error when it attempts to upload. Anyone know a workaround?

    Thanks...

    I figured it out shortly after posting my question. Since I know a lot of people use godaddy around here I'll post the answer. If you have Frontpage extensions turned on you can't change permissions - so turning off Frontpage extensions makes the "Directory Management" options available.
    Thursday, May 25, 2006 5:34 PM
  • User-1145534501 posted
    Tried converting MrLunch's code to C# with a VB to C# converter and had no luck. Anyone else successfully done this? Thanks!
    Thursday, June 8, 2006 7:52 PM
  • User654902800 posted

    Tried converting MrLunch's code to C# with a VB to C# converter and had no luck. Anyone else successfully done this? Thanks!

    The ImageFetch.ashx that in VB will run in a C# site, just replace it.

    The ImageHandling.cs code, you just need to add this method:

        public static void SaveImageFile(byte[] BytesOriginal, String filename)
        {
            String path = HttpContext.Current.Server.MapPath("~/photos/" + filename);
            FileStream f = new FileStream(path, FileMode.Create);
            f.Write(BytesOriginal, 0, BytesOriginal.Length);
            f.Close();
        }

    and change this method to:

        public static int uploadImage(string title, int albumid, Stream data)
        {
            int length = Convert.ToInt32(data.Length);
            byte[] origImageData = new byte[length];
            data.Read(origImageData, 0, length);

            SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ClubSiteDB"].ConnectionString);
            SqlCommand command = new SqlCommand("INSERT INTO Images (title, album) VALUES ( @title, @albumid); select SCOPE_IDENTITY()", connection);

            SqlParameter param0 = new SqlParameter("@title", SqlDbType.VarChar, 50);
            param0.Value = title;
            command.Parameters.Add(param0);

            SqlParameter param1 = new SqlParameter("@albumid", SqlDbType.Int);
            param1.Value = albumid;
            command.Parameters.Add(param1);

            connection.Open();

            object result = command.ExecuteScalar();
            connection.Close();
            if (result != null)
            {
                String filename = result.ToString();
                //now get the images into the file system
                //the original is already in a byte array
                SaveImageFile(origImageData, filename + ".full.jpg");
                //get the other two
                byte[] foo = MakeThumb(origImageData, 69, 69);
                SaveImageFile(foo, filename + ".thumb.jpg");
                byte[] bar = MakeThumb(origImageData, 350);
                SaveImageFile(bar, filename + ".large.jpg");
                return System.Convert.ToInt32(result);
            }
            else
            {
                return 0;
            }
        }

    I didn't test this or compile it (I use the db) so it may have typos in it, but you should get the idea.

    Those online converters are dicey. Decide to become bilingual and you'll have more fun. :)

    "Give a man a fish and you feed him for a day...
    Teach a man to fish and he sits in a boat and drinks all day"

    Monday, June 12, 2006 3:12 PM
  • User523970105 posted

    Many thanks to everyone who has contributed to this so far.  I have it working to this point, and want to finish it out.

    The only missing piece that I see is the photo delete.  It deletes the record from the database, but doesn't delete the 3 images from the file system.

    Since I'm still green as grass at .NET, I haven't been able to figure out how to tie into the delete operation to delete the files.  If I can just get the photo id passed into a C# method, I can handle the rest.

    I see that the delete button is a command button.  Can I just add an OnClick="filesys_delete" without messing up the command button functionality?  If so, how do I either pass in or retrieve the photo id?

    Any pointers would be appreciated.

    Wednesday, August 23, 2006 11:31 PM
  • User523970105 posted

    Many thanks to everyone who has contributed to this so far.  I have it working to this point, and want to finish it out.

    The only missing piece that I see is the photo delete.  It deletes the record from the database, but doesn't delete the 3 images from the file system.

    --SNIP--

    I think I figured out the answer to my own question.  Just in case anyone else is having problems, here's how to delete the files from the filesystem when the user clicks the delete button.  There is probably a better way to do this, but I'm still learning.

    Add the following in file app_code/ImageHandling.cs:

    public static void deleteImage(String id)
    {
        S
    tring partial_path = HttpContext.Current.Server.MapPath("~/photos/" + id);
        File.Delete(partial_path + ".full.jpg");
        File.Delete(partial_path + ".thumb.jpg");
        File.Delete(partial_path + ".large.jpg");
    }

    Add the following script to PhotoAlbum_Contents.aspx:

    protected void FormView2_ItemDeleting(object sender, FormViewDeleteEventArgs e)
    {
        // The three versions of the image (full, large, and thumbnail) are now
        // stored in the filesystem instead of the database. When the user deletes
        // an image, the filesystem files must also be deleted.
        ImageUtils.deleteImage(e.Keys["id"].ToString());
    }

    Now find FormView2 and add the following property:

    OnItemDeleting="FormView2_ItemDeleting"

    I haven't tested this extensively, but it seems to be working.

    Monday, August 28, 2006 3:18 AM
  • User8043363 posted

    If you would like to increase the quality of your resized images, I've done some research to find this method below works very well:

    public static byte[] MakeThumb(byte[] fullsize, int maxwidth)

       {

          Image iOriginal, iThumb;

          double scale;

          iOriginal = Image.FromStream(new MemoryStream(fullsize));

          if (iOriginal.Width > maxwidth)

          {

             scale = (double)iOriginal.Width / (double)maxwidth;

             int newheight = Convert.ToInt32(iOriginal.Height / scale);

             iThumb = new Bitmap(iOriginal, maxwidth, newheight);

             Graphics iGraphic = Graphics.FromImage(iThumb);

             iGraphic.CompositingQuality = CompositingQuality.HighQuality;

             iGraphic.SmoothingMode = SmoothingMode.HighQuality;

             iGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;

             MemoryStream m = new MemoryStream();

             Rectangle iRect = new Rectangle(0, 0, maxwidth, newheight);

             iGraphic.DrawImage(iOriginal, iRect);

             iGraphic.Save();

             iThumb.Save(m, ImageFormat.Jpeg);

             return m.GetBuffer();

          }

          else

          {

             return fullsize;

          }

       }

    Cheers,
    pixelbobby

    Friday, September 29, 2006 11:10 PM