in

dashCommerce

An ASP.NET Open Source e-Commerce Application

Force uploaded images to bend to your will!

Last post 05-14-2008 3:57 PM by leftend. 8 replies.
Page 1 of 1 (9 items)
Sort Posts: Previous Next
  • 04-25-2008 7:11 PM

    • leftend
    • Top 75 Contributor
    • Joined on 03-05-2008
    • San Diego, CA
    • Posts 12

    Force uploaded images to bend to your will!

    I was just looking at this bug fix (http://www.codeplex.com/dashCommerce/WorkItem/View.aspx?WorkItemId=15303), and thought I would share how I addressed the problem.  (I posted a comment there as well, but thought I would post it here too, since I read the forums more often).  I ended up making a few changes that save 3 versions of every product image as soon as it is uploaded (normal size, thumbnail, and a tiny one).  Please note: This ONLY applies to product image uploads.

    This may be a bit overkill - but personally, if I'm going to display an image at a number of different sizes - I like to save them at the size that they will be displayed.  This way, if I have a list of 20 product images, I'm not forcing the user to download images that are potentially 500K EACH!!!  The way I handled this was to save multiple versions of an image as soon as it's uploaded.  So, when you're adding an image to a product, and you upload it - I wrote some code to save the original image, as well as 2 additional versions that are resized to my specifications.  So, I'll try to explain more below...

    First, add these settings to your web.config file.  You can change the dimensions, this is just what my designer called for...

    <appSettings>
        <add key="maxWidth" value="545" />
        <add key="maxHeight" value="420" />
        <add key="maxThumbWidth" value="125" />
        <add key="maxThumbHeight" value="125" />
        <add key="maxTinyWidth" value="85" />
        <add key="maxTinyHeight" value="65" />
      </appSettings>

    Next, under the "repository/product" folder, add two folders: "thumb" and "tiny" - and make sure they have write permissions on them, just like the "repository/product" folder needs.

    Now, to the code.  Open up "Web/admin/imageselector.aspx.cs".  Within the "Constants" region, add these two lines:

    private const string PRODUCT_THUMB_IMAGE_PATH = @"~/repository/product/thumb/";
    private const string PRODUCT_TINY_IMAGE_PATH = @"~/repository/product/tiny/";

    Next, within the "Member Variables" region, add these 2 lines:

    private string pathToThumb = string.Empty;
    private string pathToTiny = string.Empty;

    Next, within the "Page Events" region, you're going to modify the "p" case in the "switch" block like so:

    case "p":
         pnlImages.GroupingText = LocalizationUtility.GetText("lblProductImages");
         path = PRODUCT_IMAGE_PATH;
         pathToThumb = PRODUCT_THUMB_IMAGE_PATH;
         pathToTiny = PRODUCT_TINY_IMAGE_PATH;
         break;

    Now, to the actual code that saves the images.  Replace the entire contents of the "btnUpload_Click" method with this:

    try {
        HttpPostedFile file = fuFile.PostedFile;
        if(file.ContentLength > 0) {
          int maxHeight = 0;
          int maxWidth = 0;
          maxHeight = Int32.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["maxHeight"]);
          maxWidth = Int32.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["maxWidth"]);

          //FileWriter fileWriter = new FileWriter();
          string finalPath = HttpContext.Current.Server.MapPath(path) + fuFile.FileName;
          //fileWriter.Write(finalPath, file.InputStream);
          ResizeAndSave(fuFile, finalPath, maxHeight, maxWidth);

          if (!string.IsNullOrEmpty(pathToThumb))
          {
              //now resize to thumb and save
              maxHeight = Int32.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["maxThumbHeight"]);
              maxWidth = Int32.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["maxThumbWidth"]);
              finalPath = HttpContext.Current.Server.MapPath(pathToThumb) + fuFile.FileName;
              ResizeAndSave(fuFile, finalPath, maxHeight, maxWidth);
          }

          if (!string.IsNullOrEmpty(pathToTiny))
          {
              //now resize to tiny and save
              maxHeight = Int32.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["maxTinyHeight"]);
              maxWidth = Int32.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["maxTinyWidth"]);
              finalPath = HttpContext.Current.Server.MapPath(pathToTiny) + fuFile.FileName;
              ResizeAndSave(fuFile, finalPath, maxHeight, maxWidth);
          }

          LoadImageList();
          Master.MessageCenter.DisplaySuccessMessage(LocalizationUtility.GetText("lblImageSaved"));
        }
    }
    catch(Exception ex) {
        Logger.Error(typeof(imageselector).Name + ".btnUpload_Click", ex);
        Master.MessageCenter.DisplayCriticalMessage(LocalizationUtility.GetCriticalMessageText(ex.Message));
    }

    Next, add this new method, which does the resizing (if the image is larger than your maxHeight or maxWidth) and saving:

    private void ResizeAndSave(FileUpload file, string finalPath, int maxHeight, int maxWidth)
    {
      // Create a bitmap in memory of the content of the fileUpload control
      System.Drawing.Bitmap originalBMP = new System.Drawing.Bitmap(file.FileContent);

      // Calculate the new image dimensions
      int width = originalBMP.Width;               //actual width
      int height = originalBMP.Height;             //actual height
      int widthDiff = (width - maxWidth);       //how far off maxWidth?
      int heightDiff = (height - maxHeight);    //how far off maxHeight?

      //figure out which dimension is further outside the max size
      bool doWidthResize = (maxWidth > 0 && width > maxWidth && widthDiff > -1 && widthDiff > heightDiff);
      bool doHeightResize = (maxHeight > 0 && height > maxHeight && heightDiff > -1 && heightDiff > widthDiff);

      //only resize if the image is bigger than the max
      if (doWidthResize || doHeightResize)
      {
          int iStart;
          Decimal divider;
          if (doWidthResize)
          {
              iStart = width;
              divider = Math.Abs((Decimal)iStart / (Decimal)maxWidth);
              width = maxWidth;
              height = (int)Math.Round((Decimal)(height / divider));
          }
          else
          {
              iStart = height;
              divider = Math.Abs((Decimal)iStart / (Decimal)maxHeight);
              height = maxHeight;
              width = (int)Math.Round((Decimal)(width / divider));
          }
      }

      // Create a new bitmap which will hold the previous resized bitmap
      System.Drawing.Bitmap newBMP = new System.Drawing.Bitmap(originalBMP, width, height);

      // Create a graphic based on the new bitmap
      System.Drawing.Graphics oGraphics = System.Drawing.Graphics.FromImage(newBMP);

      // Set the properties for the new graphic file
      oGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
      oGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

      // Draw the new graphic based on the resized bitmap
      oGraphics.DrawImage(originalBMP, 0, 0, width, height);

      // Save the new graphic file to the server
      newBMP.Save(finalPath);

      // Once finished with the bitmap objects, we deallocate them.
      originalBMP = null;
      newBMP = null;
      oGraphics = null;
    }

    FINALLY, you'll want to change your code to actually USE these optimized images when appropriate.  First, in "Web/Controls/catalogList.ascx.cs", find where the "thumb.ImageUrl" is set, and modify the url:

    System.Web.UI.WebControls.Image thumb = e.Item.FindControl("imgThumb") as System.Web.UI.WebControls.Image;
    if (thumb != null && !string.IsNullOrEmpty(product.DefaultImagePath) && File.Exists(Server.MapPath(product.DefaultImagePath)))
        thumb.ImageUrl = product.DefaultImagePath.Replace("product/", "product/thumb/");
       
    Then, in the "Web/product.aspx.cs" file, replace the entire "LoadProductImages" method with this version:

    /// <summary>
    /// Loads the product images.
    /// </summary>
    private void LoadProductImages () {
      ImageCollection imageCollection = _product.ImageRecords();
      imageCollection.Sort("SortOrder", true);
      Img.Image drawnImage;
      System.Web.UI.WebControls.Image displayImage;
      foreach (Store.Image image in imageCollection) {
        if (File.Exists(Server.MapPath(image.ImageFile))) {
          string origPath = image.ImageFile;
          string tinyPath = origPath.Replace("product/", "product/tiny/");
          drawnImage = Img.Image.FromFile(Server.MapPath(tinyPath));
          displayImage = new System.Web.UI.WebControls.Image();
          displayImage.ImageUrl = tinyPath;
          displayImage.Attributes.Add("onmouseover", string.Format("{0}.src='{1}';document.getElementById('{2}').innerHTML='{3}';", defaultImage.ClientID, Page.ResolveUrl(origPath), imageCaption.ClientID, image.Caption));
          displayImage.Width = drawnImage.Width;
          displayImage.Height = drawnImage.Height;
          imageList.Add(displayImage);
        }
      }
      imageList.TrimExcess();
      dlImages.DataSource = imageList;
      dlImages.DataBind();
      if (File.Exists(Server.MapPath(imageCollection[0].ImageFile))) {
        defaultImage.ImageUrl = imageCollection[0].ImageFile;
        drawnImage = Img.Image.FromFile(Server.MapPath(imageCollection[0].ImageFile));
        defaultImage.Width = drawnImage.Width;
        defaultImage.Height = drawnImage.Height;
      }
      imageCaption.Text = imageCollection[0].Caption;
    }

    One last thing... I promise!!!  (I told you this might be a bit overkill...).  Find the "site.skin" file in the App_Themes folder that you are using, and change this:

    <asp:Image runat="server" ImageUrl="images/noDefaultImage.gif" height="450px" SkinId="noImage"/>

    to this:

    <asp:Image runat="server" ImageUrl="images/noDefaultImage.gif" SkinId="noImage"/>

    The "height" overrides ANYTHING you try to set from the code.

    Whew!!!  That's it!  I hope that helps someone out there...

    Filed under: , ,
  • 05-07-2008 4:08 AM In reply to

    • Naz
    • Top 10 Contributor
    • Joined on 02-11-2008
    • Posts 60
    • dashCommerce Core Team

    Re: Force uploaded images to bend to your will!

    Added this feature in revision 92 thanks for your help.
    www.objectreference.net - My Blog
    www.vortexleather.com - My Leather clothing store

    Find a bug? Create a Work Item for a fast response.. Want to help? Create a patch for us!
  • 05-07-2008 12:26 PM In reply to

    • leftend
    • Top 75 Contributor
    • Joined on 03-05-2008
    • San Diego, CA
    • Posts 12

    Re: Force uploaded images to bend to your will!

     My pleasure, glad it made the cut!

  • 05-13-2008 11:34 AM In reply to

    Yes [Y] Re: Force uploaded images to bend to your will!

    I did something else!!

    I actually included a source code that I used in our website troovtoo.ma. I added in the dashcommerce project a new Aspx WebForm called ViewImage.aspx. This aspx read the image and write it rescaled and centered in the size that we specify in the parameters.

    When I specify to the web browser http://mydashcommercesite/ViewImage?img=~/repository/product/img.jpg&width=100&height=100, ViewImage translate the file in the img parameter to a physical path then resize the image and write back the image in the outputstream.

    When I have my viewimage.aspx coded, instead of specifying the direct url to imageURL in the other pages(ex:product.aspx) I change it to viewImage with the correct parameters.

    Now my dashcommerce is working exceptionally good Big Smile

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

    Faical Said

    HighWave Creations

    Marrakech, Morocco

    -----------------------------------------
    Faical Said
    HighWave Creations
    Marrakech, Morocco
    Filed under: , , ,
  • 05-13-2008 9:30 PM In reply to

    Re: Force uploaded images to bend to your will!

    @leftend
    I see that if both the Height and the Width are the same then it doesn't resize the image

    This should help can some one confirm this.

    if (doWidthResize || doHeightResize) {
              int iStart;
              Decimal divider;
              if (doWidthResize) {
                iStart = width;
                divider = Math.Abs((Decimal)iStart / (Decimal)maxWidth);
                width = maxWidth;
                height = (int)Math.Round((Decimal)(height / divider));
              } else {
                iStart = height;
                divider = Math.Abs((Decimal)iStart / (Decimal)maxHeight);
                height = maxHeight;
                width = (int)Math.Round((Decimal)(width / divider));
              }
            }

    else {
                height = maxHeight;
                width = maxWidth;
    }

    Find a bug? Create a Work Item for a fast response.. Want to help? Create a patch for us!
  • 05-14-2008 4:12 AM In reply to

    • leftend
    • Top 75 Contributor
    • Joined on 03-05-2008
    • San Diego, CA
    • Posts 12

    Re: Force uploaded images to bend to your will!

     @yitzchok

    Thanks for the heads up, yep, you're right... mostly.  If the maxWidth and maxHeight are different - then the math will still work, and the image will get resized.  However, if the image is square, and the max width/height are the same - then no resize is performed.

    I would change your solution, only because it could actually scale the image to be larger if the user uploads a small image.  I think the following block of code should fix it.  I would place it right after the widthDiff and heightDiff are calculated:

    int widthDiff = (width - maxWidth);       //how far off maxWidth?
    int heightDiff = (height - maxHeight);    //how far off maxHeight?

    //catch case where image is square, and the diffs are the same
    if (width.Equals(height) && widthDiff.Equals(heightDiff))
    {
          //force width to win - if square, doesn't matter which side is resized
          widthDiff++;
    }

    This should make the "doWidthResize" variable be set to true.  Since the "widthDiff" and "heightDiff" variables aren't actually used in the resizing calculations, I feel more comfortable modifying one of them.

    Thanks again!  I'll try to submit a patch. 

  • 05-14-2008 7:45 AM In reply to

    Re: Force uploaded images to bend to your will!

    Here is Our Source use it in any way you want Yes

    ------------------------------------------------ViewImage.aspx----------------------------------------------------

    using System;

    using System.Data;

    using System.Configuration;

    using System.Collections;

    using System.Web;

    using System.Web.Security;

    using System.Web.UI;

    using System.Web.UI.WebControls;

    using System.Web.UI.WebControls.WebParts;

    using System.Web.UI.HtmlControls;

    using System.IO;

    using System.Drawing;

    namespace MettleSystems.dashCommerce.Web

    {

    public partial class ViewImage : System.Web.UI.Page

    {

    // By HighWave Creations © 2008

    private int PHOTOWIDTH = 100; private int PHOTOHEIGHT = 100;

    private string url = "";

    protected void Page_Load(object sender, EventArgs e)

    {

    PHOTOWIDTH = Convert.ToInt32((Request.QueryString["width"]));

    PHOTOHEIGHT = Convert.ToInt32(Request.QueryString["height"]);

    url = Request.QueryString["url"];

    url = url.Replace("~","");url = url.Replace(@"/", @"\");

    url = Request.PhysicalApplicationPath + url.Remove(0,1);

    Byte[ img = resizePic(url);

    Response.OutputStream.Write(img, 0, img.Length);

    }

    /// <summary>

    /// Retoune un byte array d'une image selon la taille demandée

    /// </summary>

    /// <param name="src">l'image à changer</param>

    private byte[ resizePic(string src)

    {

    Bitmap source;

    MemoryStream stream = new MemoryStream();

    int x,y; Double coefficient;

    //exit sub if no picture

    if (src.Trim() == null) return null;

    //get the source image

    source = new Bitmap(src);if (source.Width > source.Height)

    {

    coefficient = (
    double)PHOTOWIDTH / source.Width;

    x = PHOTOWIDTH;

    y =
    Convert.ToInt32(source.Height * coefficient);

    }else

    {

    coefficient = (
    double)PHOTOHEIGHT / source.Height;x = Convert.ToInt32(source.Width * coefficient);

    y = PHOTOHEIGHT;

    }

    Bitmap rescaled = new Bitmap(PHOTOWIDTH, PHOTOHEIGHT); Graphics gr = Graphics.FromImage(rescaled);

    //Fill with white

    System.Drawing.Rectangle rect = new Rectangle();

    rect.X = 0;

    rect.Y = 0;

    rect.Width = PHOTOWIDTH;

    rect.Height = PHOTOHEIGHT;

    gr.FillRectangle(
    Brushes.White, rect);

    //Antialias

    gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;

    //rescale image

    int oX = Convert.ToInt32(Math.Floor((decimal)(PHOTOWIDTH - x) / 2));int oY = Convert.ToInt32(Math.Floor((decimal)(PHOTOHEIGHT - y) / 2));

    gr.DrawImage(source, oX, oY, x, y);

    //Save to a memorystream

    rescaled.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);

    //Put stream byte array to the field photo

    return stream.GetBuffer();

    }

    }

    }

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

    Any where you want to access this page you just specify three parameteres url(of the image), width, and height as the following: http://mydomaine/ViewImage.aspx?url=~/repository/product/LE37R86B-ret.jpg&width=200&height=200

    The new LoadProductImages function in product.aspx is as the following:

    --------------------------------------------------Product.aspx-----------------------------------------------------------

    //...................... 

    /// <summary>

    /// Loads the product images.

    /// </summary>

    private void LoadProductImages() {

    ImageCollection imageCollection = Store.Caching.ProductCache.GetImageCollectionByProduct(_product);

    if(imageCollection.Count > 0) {

    imageCollection.Sort("SortOrder", true);

    Img.Image drawnImage;

    System.Web.UI.WebControls.Image displayImage;

    foreach(Store.Image image in imageCollection) {

    if(File.Exists(Server.MapPath(image.ImageFile))) {

    drawnImage = Img.Image.FromFile(Server.MapPath(image.ImageFile)); displayImage = new System.Web.UI.WebControls.Image();

    ////////////////What HighWave Creations modified//////////////////

    displayImage.ImageUrl = "ViewImage.aspx?url=" + image.ImageFile + "&width=200&height=200";

    //////////////////////////////////////////////////////////////////

    displayImage.Attributes.Add("onmouseover", string.Format("{0}.src='{1}';document.getElementById('{2}').innerHTML='{3}';", defaultImage.ClientID, Page.ResolveUrl(displayImage.ImageUrl), imageCaption.ClientID, image.Caption));

    if(drawnImage.Width > drawnImage.Height) {

    if(drawnImage.Width > 32) {

    displayImage.Width = 32;

    displayImage.Height = drawnImage.Height * 32 / drawnImage.Width;

    }

    }

    else { //portrait

    if(drawnImage.Height > 32) {

    displayImage.Height = 32;

    displayImage.Width = drawnImage.Width * 32 / drawnImage.Height;

    }

    }

    imageList.Add(displayImage);

    }

    }

    imageList.TrimExcess();

    dlImages.DataSource = imageList;

    dlImages.DataBind();

    ////////////////What HighWave Creations modified//////////////////

    if(File.Exists(Server.MapPath(imageCollection[0].ImageFile))) {defaultImage.ImageUrl = "ViewImage.aspx?url=" + imageCollection[0].ImageFile + "&width=200&height=200";

    ////////////////////////////////////////////////////////////////////////////////////

    }

    imageCaption.Text = imageCollection[0].Caption;

    }

    //........................................

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

    Enjoy. Smile

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

    Faical Said

    HighWave Creations

    Marrakech, Morocco

    -----------------------------------------
    Faical Said
    HighWave Creations
    Marrakech, Morocco
    Filed under: , , , ,
  • 05-14-2008 3:32 PM In reply to

    • Naz
    • Top 10 Contributor
    • Joined on 02-11-2008
    • Posts 60
    • dashCommerce Core Team

    Re: Force uploaded images to bend to your will!

    @faicalsaid

    I see how you've implemented that it's pritty cool feature that you can request any image in any size but processing every image request through that handler and dynamically resizing it for every request can bring down performance for heavy sites as now your webserver will have to process every image.. It's better to cache them into the filesystem.

    Also your not disposing of the bitmaps after processing them. .NET GDI+ objects like Bitmaps, Graphics etc can use a lot of memory and it's recommended you call Dispose() after finising with them rather than waiting for the GC.

     @LeftEnd

    Thanks for your patch on codeplex i've implemented a fix based on it.

     

     

     

    www.objectreference.net - My Blog
    www.vortexleather.com - My Leather clothing store

    Find a bug? Create a Work Item for a fast response.. Want to help? Create a patch for us!
  • 05-14-2008 3:57 PM In reply to

    • leftend
    • Top 75 Contributor
    • Joined on 03-05-2008
    • San Diego, CA
    • Posts 12

    Re: Force uploaded images to bend to your will!

     @Naz

    No problem.  It's the first time I looked at what you implemented.  I like the changes/refactoring that you did.

Page 1 of 1 (9 items)