Click here to Skip to main content
15,895,142 members
Articles / Web Development / HTML

Generate CSS sprites and thumbnail images on the fly in ASP.NET sites

Rate me:
Please Sign up or sign in to vote.
4.82/5 (40 votes)
9 Jun 2012CPOL64 min read 117.6K   2.8K   85  
Reduces page load times of ASP.NET web sites by combining page images and CSS background images into CSS sprites. Compresses and physically resizes images to make thumbnails. Caters for repeating background images.
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using Mapper;
using System.Drawing;
using System.IO;
using System.Web;

namespace CssSpriteGenerator
{
    /// <summary>
    /// Represents an individual image as used on the page or in the css
    /// (that is, before it gets replaced by a sprite).
    /// </summary>
    public class ImageInfo : IImageInfo, IComparable<ImageInfo>
    {
        private class Sizes
        {
            // _reportedWidth and _reportedHeight holds the width and height of the image as reported to the outside world.
            // It is based not only on the bitmap, but also on _resizeSize, _resizeSizeOverride.
            // Calculating this may not involve the bitmap at all, if the width and height can be found from those 2 "resize..." variables.
            // If a value is not known, it is set to -1.
            private Size _reportedSize = new Size(-1, -1);

            // An image may get resized for a couple of reasons:
            // * the image tag contains a width and/or height property and the physical dimensions of the image are different.
            //   This can be disabled with the group property DisableAutoResize.
            //   For restrictions, see the DisableAutoResize property of classGroup.
            // * the image is addded to a group with ResizeHeight or ResizeWidth.
            //
            // This Size can have these values:
            // -2 means "not set". If one is -2, the other should be -2 as well. -1 means "use physical size. 
            // * If both are -2 or -1, use the physical size of the image
            // * If one is -1 and the other is > -1, the aspect ratio will be used to deduce the one with -1.
            private Size _resizeSize = new Size(-2, -2);

            // The overrides are used to override the image's "normal" size.
            // They get set based on the group's ResizeWidth and ResizeHeight.
            // -1 means "not set" (so that's different from _resizeSize)
            private Size _resizeSizeOverride = new Size(-1, -1);

            public Size ReportedSize
            {
                // When setting resizeSize, also discard the reported size
                set { _reportedSize = value; }
                get { return _reportedSize; }
            }

            public Size ResizeSize
            {
                // When setting resizeSize, also discard the reported size
                set { _resizeSize = value; _reportedSize = new Size(-1, -1); }
                get { return _resizeSize; }
            }

            public Size ResizeSizeOverride 
            {
                // When setting resizeSizeOverride, also discard the reported size
                set { _resizeSizeOverride = value; _reportedSize = new Size(-1, -1); }
                get { return _resizeSizeOverride; }
            }

            public bool ReportedSizeSet()
            {
                return ((_reportedSize.Width != -1) && (_reportedSize.Height != -1));
            }

            // Note that if Width and Height are -1, than they have been set.
            public bool ResizeSizeSet()
            {
                return ((_resizeSize.Width != -2) && (_resizeSize.Height != -2));
            }

            // If both width and height of the override are -1, then there is no override
            public bool ResizeSizeOverrideSet()
            {
                return ((_resizeSizeOverride.Width != -1) || (_resizeSizeOverride.Height != -1));
            }
        }

        // ------------------

        private StoredImage _storedImage = new StoredImage();
        private Sizes _sizes = new Sizes();

        private bool _isCombinable = true;
        private bool _hasPageBasedReferences = false;

        List<IImageReference> _imageReferences = new List<IImageReference>();

        /// <summary>
        /// Most restrictive combine restriction of all image references.
        /// </summary>
        private CombineRestriction _combineRestriction = CombineRestriction.None;

        public Size ResizeSizeOverride { set { _sizes.ResizeSizeOverride = value; } }

        /// <summary>
        /// True if it is known that the OriginalImageFilePath is broken
        /// (that is, doesn't point to a file, or the file cannot be read into a bitmap).
        /// </summary>
        /// <param name="url"></param>
        public bool FilePathBroken
        {
            get { return _storedImage.FilePathBroken; }
        }

        /// <summary>
        /// Gets the full image file path of the image, such as 
        /// c:\website\images\smallicon.png
        /// </summary>
        /// <returns></returns>
        public string OriginalImageFilePath
        {
            get { return _storedImage.OriginalImageFilePath; }
        }

        /// <summary>
        /// Gets the image type based on the extension of the full image file path of the image
        /// TODO: performance: could store this in a private field.
        /// </summary>
        /// <returns></returns>
        public ImageType ImageType
        {
            get { return UrlUtils.ImageTypeUrl(OriginalImageFilePath); }
        }

        /// <summary>
        /// Width in px of the image.
        /// </summary>
        public int Width
        {
            get 
            {
                return ReportedSize.Width;
            }
        }

        /// <summary>
        /// Height in px of the image
        /// </summary>
        public int Height
        {
            get
            {
                return ReportedSize.Height;
            }
        }

        /// <summary>
        /// Size of the image after it has been optionally resized (eg. because of group properties).
        /// </summary>
        public Size ReportedSize
        {
            get { return GetReportedSize(); }
        }

        /// <summary>
        /// Size of the original image on disk
        /// </summary>
        public Size OriginalSize
        {
            get { return _storedImage.OriginalImageSize(); }
        }

        /// <summary>
        /// Size in bytes of the image file. This is its actual size, not the size on disk.
        /// </summary>
        public long ImageFileSize
        {
            get
            {
                return _storedImage.GetImageFileSize();
            }
        }

        /// <summary>
        /// Bitmap representing the image
        /// </summary>
        public Bitmap ImageBitmap
        {
            get
            {
                return _storedImage.ImageBitmap(GetReportedSize());
            }
        }

        /// <summary>
        /// File path of the image on disk.
        /// </summary>
        public string ImageFilePath
        {
            get
            {
                return _storedImage.OriginalImageFilePath;
            }
        }

        /// <summary>
        /// List of all the image references that use this image.
        /// </summary>
        public List<IImageReference> ImageReferences
        {
            get { return _imageReferences; }
        }

        /// <summary>
        /// If this returns false, than this ImageInfo is not actually combinable with anything else.
        /// This could be because the image references that share the same image url have 
        /// conflicting combine restrictions.
        /// </summary>
        public bool IsCombinable
        {
            get { return _isCombinable; }
        }

        /// <summary>
        /// True if this ImageInfo has references to images used on the page.
        /// False if all references in this ImageInfo are either folder references (which were read from 
        /// a disk folder) or from the CSS images collection.
        /// </summary>
        public bool HasPageBasedReferences
        {
            get { return _hasPageBasedReferences; }
        }

        /// <summary>
        /// Strictest combine restriction of all image references in this ImageInfo.
        /// </summary>
        public CombineRestriction CombineRestriction
        {
            get { return _combineRestriction; }
        }

        /// <summary>
        /// Used by a IMapper. When it has processed this ImageInfo, it sets this flag to true.
        /// </summary>
        public bool Processed { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="firstImageReference">
        /// First image reference to be added to the ImageInfo.
        /// Note that there is no point in having an ImageInfo without image references.
        /// 
        /// It is up to the caller to make sure that firstImageReference.OriginalImageFilePath
        /// is an image that lives on the web server (not external, not broken).
        /// </param>
        public ImageInfo(IImageReference firstImageReference)
        {
            _storedImage.OriginalImageFilePath = firstImageReference.OriginalImageFilePath;
            _sizes.ResizeSize = firstImageReference.SizeFromProperties;

            AddImageReference(firstImageReference);
            Processed = false;
        }

        /// <summary>
        /// Disposes the bitmap related to the image.
        /// Does nothing if there is no bitmap.
        /// 
        /// You can still use the ImageInfo object after calling this.
        /// If you access a property that requires the bitmap, it will simply load it again.
        /// </summary>
        public void DisposeBitmap()
        {
            _storedImage.DisposeBitmap();
        }

        /// <summary>
        /// Call this to stop the ImageInfo from automatically resizing the image
        /// based on width and height properties. Does not affect the dimensions set
        /// with ResizeWidthOverride and ResizeHeightOverride.
        /// </summary>
        public void DisableAutoResize()
        {
            _sizes.ResizeSize = new Size(-1, -1);
        }

        /// <summary>
        /// Returns true if the ImageInfo was instructed to resize the image, 
        /// via auto resize (based on width or height properties of img tag)
        /// or via ResizeSizeOverride property.
        /// </summary>
        public bool MadeToResize()
        {
            bool madeToResize = 
                (!SizeUtils.IsEmptySize(_sizes.ResizeSizeOverride)) ||
                (!SizeUtils.IsEmptySize(_sizes.ResizeSize));
             
            return madeToResize;
        }

        /// <summary>
        /// Returns true if the image is animated.
        /// 
        /// We're assuming here that only .gif images can be animated.
        /// </summary>
        /// <returns></returns>
        public bool IsAnimated()
        {
            bool? isBitmapAnimated = CacheUtils.FileBasedEntry<bool?>(CacheUtils.CacheEntryId.IsAnimated, OriginalImageFilePath);
            if (isBitmapAnimated == null)
            {
                // If the image is not a .gif, assume it isn't animated.
                ImageType imageType = UrlUtils.ImageTypeUrl(OriginalImageFilePath);
                if (imageType != ImageType.Gif)
                {
                    isBitmapAnimated = false;
                }
                else
                {
                    // Check the bitmap itself.
                    isBitmapAnimated = ImageUtils.IsAnimated(ImageBitmap);
                }

                CacheUtils.InsertFileBasedEntry<bool?>(CacheUtils.CacheEntryId.IsAnimated, OriginalImageFilePath, isBitmapAnimated);
            }

            return (bool)isBitmapAnimated;
        }

        /// <summary>
        /// Merges another image info into this image info.
        ///
        /// IMPORTANT:
        /// Only use this if you know that the other image info has the same reportedSize as this one
        /// (for example, if both this image info and the other one are part of a group with ResizeWidth and/or ResizeHeight set).
        /// 
        /// Also, this doesn't take Processed into account.
        /// </summary>
        /// <param name="otherImageInfo"></param>
        public void Merge(ImageInfo otherImageInfo)
        {
            // In the end, an ImageInfo is defined by its image references. So add those of the other image info.

            foreach (IImageReference imageReference in otherImageInfo.ImageReferences)
            {
                AddImageReference(imageReference);
            }
        }

        /// <summary>
        /// Returns a string that uniquely identifies this ImageInfo
        /// </summary>
        /// <returns></returns>
        public string UniqueId()
        {
            Size size = GetReportedSize();

            //TODO: ImageFilePath contains the root dir of the web site itself.
            // You could remove that and save some space in the string.
            // When UniqueId is used to create a cache key based on lots of ImageInfos,
            // this redundancy makes for a very long cache key.

            string result = ImageFilePath + "|" + size.Width.ToString() + "|" + size.Height.ToString();
            return result;
        }

        /// <summary>
        /// Implementation of CompareTo, to implement IComparable interface
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo(ImageInfo other)
        {
            // If other is not a valid object reference, this instance is greater.
            if (other == null) return 1;

            return string.Compare(UniqueId(), other.UniqueId());
        }


        /// <summary>
        /// Override the ToString method, to get a better readout when looking at lists of ImageInfos.
        /// Note that calling this is likely to load the bitmap.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            string result = string.Format(
                "ImageInfo: {0} | {1} x {2}{3}{4}{5}{6}",
                ImageFilePath,
                Width, Height,
                (IsCombinable ? "" : " | Not Combinable"),
                (HasPageBasedReferences ? "" : " | No Page Based References"),
                ((CombineRestriction == CombineRestriction.HorizontalOnly) || 
                 (CombineRestriction == CombineRestriction.VerticalOnly) ? " | " + CombineRestriction.ToString() : ""),
                (Processed ? " | Processed" : ""));

            return result;
        }

        /// <summary>
        /// Retrieves the reported size of the image from _reportedSize. 
        /// 
        /// Recalculates the reported size of the image if the reported size is not available.
        /// This is based on _resizeSize and _resizeSizeOverride.
        /// 
        /// This may involve loading the bitmap if not enough width and height info is available
        /// through the _storedBitmap object. 
        /// </summary>
        /// <returns>
        /// The reported size.
        /// </returns>
        private Size GetReportedSize()
        {
            // TODO: This method is called often, eg. to find the unique id of this ImageInfo, which in turn is used to sort the images
            // in GroupInfo.GenerateSprite to find the cache key of the sprite (every time a sprite is generated).
            // Make this a bit faster by storing the reported size in ImageInfo, wiping the stored info if any of the input sizes are set.
            // (use set accessor).

            if (_sizes.ReportedSizeSet()) { return _sizes.ReportedSize; }

            Size size = _sizes.ResizeSize;
            if (_sizes.ResizeSizeOverrideSet()) { size = _sizes.ResizeSizeOverride; }

            _sizes.ReportedSize = CompletedSize(size);

            return _sizes.ReportedSize;
        }


        /// <summary>
        /// Takes a size, and makes it complete (so both width and height are set).
        /// Returns the completed size.
        /// 
        /// If both width and height are set, the size is returned as is.
        /// If neither are set, the original dimensions of the image in this ImageInfo are used.
        /// If only one is set, the other is set based on the aspect ratio of the original dimensions.
        /// </summary>
        /// <param name="size"></param>
        private Size CompletedSize(Size size)
        {
            Size newSize;

            if (SizeUtils.IsCompleteSize(size)) 
            {
                // We have both the width and height. No need to do anything.
                newSize = size;
            }
            else if (SizeUtils.IsEmptySize(size))
            {
                // If both width and height are empty, use the physical dimensions of the image.
                newSize = _storedImage.OriginalImageSize();
            }
            else
            {
                newSize = SizeUtils.CompletedSize(size, _storedImage.OriginalAspectRatio());
            }

            return newSize;
        }

        /// <summary>
        /// Replaces all referred images (in the ImageReferences collection)
        /// with the given sprite.
        /// </summary>
        /// <param name="spriteUrl">
        /// Url of the sprite. The sprite is an image in which one or more images have
        /// been combined, including the original image.
        /// </param>
        /// <param name="xOffset">
        /// X offset within the sprite where the original image starts.
        /// </param>
        /// <param name="yOffset">
        /// Y offset within the sprite where the original image starts.
        /// </param>
        /// <param name="additionalCss">
        /// If replacing the original image means that additional CSS needs to be sent to the browser
        /// (after the original CSS has been sent), add the additional CSS to this Stylesheet.
        /// 
        /// The caller of this method
        /// is responsible for putting the CSS in a stylesheet and having it linked to from the page.
        /// </param>
        /// <param name="nbrImagesInSprite">
        /// Number of images in the sprite identified by spriteUrl.
        /// </param>
        public void ReplaceAllReferredImagesWithSprite(
            string spriteUrl, int xOffset, int yOffset,
            Stylesheet additionalCss, int nbrImagesInSprite)
        {
            // No point in replacing anything if the image file path is broken.
            // In that case, you want to keep the original img tag, to make it easier for the user
            // to find the broken src.
            if (FilePathBroken) { return; }

            foreach (IImageReference imageReference in ImageReferences)
            {
                imageReference.ReplaceWithSprite(
                    spriteUrl, xOffset, yOffset,
                    Width, Height,
                    MadeToResize(),
                    additionalCss, nbrImagesInSprite);
            }
        }

        /// <summary>
        /// Returns true if the given imageRefence can be added to this ImageInfo.
        /// 
        /// This checks both the image path and the dimension that need to be used when showing the image on the
        /// page. For example
        /// img src="abc.png" width="100" height="100
        /// and
        /// img src="abc.png" width="200" height="200
        /// are not compatible.
        /// 
        /// Dimensions are an issue, because when images are combined into a sprite, they will be shown as
        /// background images, and background images are not resizable in CSS. So when width/height are different
        /// from the physical size and the image is to go into a sprite, the image must be auto resized!
        /// </summary>
        /// <param name="imageReference"></param>
        /// <returns></returns>
        public bool MatchesImageReference(IImageReference imageReference)
        {
            if (OriginalImageFilePath != imageReference.OriginalImageFilePath) { return false; }

            // If the file path of this image info is broken, than that of the image reference is broken as well.
            // To prevent any further processing, return true.
            // This broken image info (along with its image references) won't be processed further into a sprite, etc.
            if (FilePathBroken) { return true; }

            // If ResizeSizeOverride has been set, than this ImageInfo is evidently part of a group
            // that sets ResizeSize to give all images in its group a uniform size.
            // In that case, it is ok to add any image reference with the same file path.

            if (_sizes.ResizeSizeOverrideSet()) { return true; }

            // At this point, ResizeSizeOverride has not been set, so the size of the image will be
            // determined as far as we know now by any width and height properties on the original img
            // (causing auto resize) and the physical image size. This is all captured in
            // _sizes.ReportedSize.

            // If ResizeSize and SizeFromProperties are the same, than return true.
            // This is a common situation before any attempt has been made to match the ImageInfo with a
            // group (so GetReportedSize has never been called), and the user has either given
            // all img tags a width and height property, or has given none of them a width and height 
            // property.
            //
            // However, if you read image from disk folder, than those image references will have 
            // no SizeFromProperties, so than if img tags have width and height, you need to read the image 
            // from disk to see if those width and height are the same as the physical width and height.

            if (_sizes.ResizeSize == imageReference.SizeFromProperties) { return true; }

            // ResizeSize and SizeFromProperties may have been different because
            // ResizeSize has been set to physical size of the image, while SizeFromProperties
            // is only partially or not filled in - in which case they are really the same.

            Size completedSizeFromProperties = CompletedSize(imageReference.SizeFromProperties);
            Size reportedSize = GetReportedSize();

            return (completedSizeFromProperties == reportedSize);
        }

        /// <summary>
        /// Adds an image reference. 
        /// 
        /// Do not use ImageReferences.Add to do this.
        /// </summary>
        /// <param name="imageReference"></param>
        public void AddImageReference(IImageReference imageReference)
        {
            if (imageReference.OriginalImageFilePath != _storedImage.OriginalImageFilePath)
            {
                throw new Exception(
                    string.Format(
                        "Attempting to add an image reference with url={0} to an ImageInfo with url={1}",
                        imageReference.OriginalImageFilePath, _storedImage.OriginalImageFilePath));
            }

            // If you've already found that this ImageInfo is not combinable, no need to check further.
            if (_isCombinable && (imageReference is IImageReferenceWithCombineTypeRestriction))
            {
                IImageReferenceWithCombineTypeRestriction imageReferenceWithCombineTypeRestriction = 
                    imageReference as IImageReferenceWithCombineTypeRestriction;

                // Check if this image reference is not compatible with the other image references. If not, than this ImageInfo cannot be
                // combined with others into a sprite.
                if (!CombineRestrictionUtils.Combinable(_combineRestriction, imageReferenceWithCombineTypeRestriction.CombineRestriction))
                {
                    _isCombinable = false;
                }
                else
                {
                    // Update the overall combine restriction. 
                    // _combineRestriction contains the strictest of all image references.
                    _combineRestriction = CombineRestrictionUtils.MostRestrictive(
                        _combineRestriction, imageReferenceWithCombineTypeRestriction.CombineRestriction);
                }
            }

            if ((imageReference is PageBase_ImageReference) || (imageReference is Css_ImageReference))
            {
                _hasPageBasedReferences = true;
            }

            _imageReferences.Add(imageReference);
        }

        /// <summary>
        /// Returns true if this ImageInfo matches the given group.
        /// </summary>
        /// <param name="group"></param>
        /// <returns></returns>
        public bool MatchesGroup(Group group)
        {
            // Put the most expensive property accesses last.
            // Accessing _storedImage.OriginalImageFilePath is very cheap.
            // ImageSize involves reading file size from disk (and can throw an exception if file not found).
            // Width and Height involve reading the bitmap from disk.
            //
            // It is at this point that animated images get excluded from all groups

            if (FilePathBroken) { return false; }

            bool result = false;

            bool pathMatch = (group.FilePathMatchRegex == null) || group.FilePathMatchRegex.IsMatch(OriginalImageFilePath);
            if (pathMatch)
            {
                // Make sure that this group can be used on this page
                // Do that here instead of in ConfigSection.Groups(), because Groups() doesn't get called for every page
                // (ASP.NET seems to do some caching there).
                //
                // Check ImageFileSize against MaxSpriteSize (below). It is very easy for users to set a MaxSpriteSize
                // but than forget to restrict the group to images that are actually smaller than this.
                string currentUrl = HttpContext.Current.Request.Url.ToString();
                bool pageUrlMatches = (group.PageUrlMatchRegex == null) || group.PageUrlMatchRegex.IsMatch(currentUrl);

                result =
                    (pageUrlMatches &&
                     ((group.MaxSize == Int32.MaxValue) || (ImageFileSize <= group.MaxSize)) &&
                     ((group.MaxSpriteSize == Int32.MaxValue) || (ImageFileSize <= group.MaxSpriteSize)) &&
                     ((group.MaxWidth == Int32.MaxValue) || (Width <= group.MaxWidth)) &&
                     ((group.MaxHeight == Int32.MaxValue) || (Height <= group.MaxHeight)) &&
                     ((group.MaxPixelFormat == PixelFormat.Format64bppPArgb) || 
                        (!ImageBitmap.PixelFormat.IsHigherThan(group.MaxPixelFormat))) &&
                     (!IsAnimated()));
            }

            // We need to make sure that at some stage we can get the UniqueId of the ImageInfo, because this is used in
            // GroupInfo.GenerateSprite to sort the ImageInfos to create the cache key for the sprite.
            // If the ImageInfo has a broken image and getting the UniqueId needs to load the image to get the dimensions of the 
            // image (part of the unique id), then that would lead to an exception. This point here is by far the best place to
            // deal with that exception.
            //
            // Essentially we're not allowing ImageInfos with broken image links to join a group,
            // rather than letting it join and creating problems later.

            // Call UniqueId but throw away its return value
            if (result) { UniqueId(); }

            return result;
        }

        /// <summary>
        /// Returns true if this ImageInfo matches the given GroupInfo.
        /// </summary>
        /// <param name="groupInfo">
        /// GroupInfo to check for match.
        /// </param>
        /// <param name="group">
        /// Group that this ImageInfo belongs to
        /// </param>
        /// <returns>
        /// true: The GroupInfo is a match.
        /// false: it isn't.
        /// </returns>
        public bool MatchesGroupInfo(GroupInfo groupInfo, Group group)
        {
            if (!IsCombinable) { return false; }

            // If the image is part of a group that has GiveOwnSprite set to true, than simply return false here.
            // That will cause a new GroupInfo to be created just for this image.
            // No other image will be added to this new and exclusive GroupInfo, because all images that could be added
            // here are part of the same "don't combine" group.
            if (group.GiveOwnSprite) { return false; }

            if (group != groupInfo.Group) { return false; }

            if (!CombineRestrictionUtils.Combinable(groupInfo.CombineRestriction, CombineRestriction)) { return false; }

            if (CombineRestrictionUtils.Compare(groupInfo.CombineRestriction, CombineRestriction) < 0)
            {
                throw new Exception(
                    string.Format(
                        "Matching an ImageInfo against a GroupInfo, where the GroupInfo has less restrictive combine restrictions" +
                        " than the ImageInfo." +
                        " ImageInfo.CombineRestriction={0}, ImageInfo.OriginalImageFilePath={1}, GroupInfo.CombineRestriction={2}, GroupInfo.Group.GroupName={3}",
                        CombineRestriction, OriginalImageFilePath, groupInfo.CombineRestriction, groupInfo.Group.GroupName));
            }

            // At this point, we know that the GroupInfo has combine restrictions that are at least as restrictive as those of the ImageInfo.
            // And that they are combinable.
            // So you only need to look at the GroupInfo's combine restrictions when checking dimensions.

            // Note that we're not looking at MaxSpriteSize here, because at this stage we don't know which images will be
            // combined together into sprites by the mapper algo. It is that algo (in MapperUtils.Mapping) that takes MaxSpriteSize into account.

            if (groupInfo.CombineRestriction == CombineRestriction.HorizontalOnly)
            {
                if (Height != groupInfo.InitialImageHeight) { return false; }
            }
            else if (groupInfo.CombineRestriction == CombineRestriction.VerticalOnly)
            {
                if (Width != groupInfo.InitialImageWidth) { return false; }
            }

            // -----------

            if (group.SameImageType)
            {
                if (ImageType != groupInfo.InitialImageType) { return false; }
            }

            // If SamePixelFormat is true, the image must have the same pixel format as the images in the group,
            // and if it is a jpeg the rest of the group has to be jpeg as well and vice versa.
            if (group.SamePixelFormat)
            {
                if ((ImageBitmap.PixelFormat != groupInfo.InitialPixelFormat) ||
                    ((ImageType == ImageType.Jpg) != (groupInfo.InitialImageType == ImageType.Jpg)) ) { return false; }
            }

            return true;
        }
    }
}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Australia Australia
Twitter: @MattPerdeck
LinkedIn: au.linkedin.com/in/mattperdeck
Current project: JSNLog JavaScript Logging Package

Matt has over 9 years .NET and SQL Server development experience. Before getting into .Net, he worked on a number of systems, ranging from the largest ATM network in The Netherlands to embedded software in advanced Wide Area Networks and the largest ticketing web site in Australia. He has lived and worked in Australia, The Netherlands, Slovakia and Thailand.

He is the author of the book ASP.NET Performance Secrets (www.amazon.com/ASP-NET-Site-Performance-Secrets-Perdeck/dp/1849690685) in which he shows in clear and practical terms how to quickly find the biggest bottlenecks holding back the performance of your web site, and how to then remove those bottlenecks. The book deals with all environments affecting a web site - the web server, the database server and the browser.

Matt currently lives in Sydney, Australia. He recently worked at Readify and the global professional services company PwC. He now works at SP Health, a global provider of weight loss web sites such at CSIRO's TotalWellBeingDiet.com and BiggestLoserClub.com.

Comments and Discussions