using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mapper;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.IO;
namespace CssSpriteGenerator
{
/// <summary>
/// Represents the contents of a sprite image.
///
/// Note that this doesn't hold the image itself. That only gets generated and written to disc
/// by method WriteSpriteImage.
/// </summary>
// TODO: automatically increase pixel depth of the sprite if the constituent images all have an indexed color scheme
// but with widely different palettes.
public class SpriteInfoWritable : Sprite
{
/// <summary>
/// Generates an image containing the individual images as described in spriteMappings.
/// Writes the image to disc.
///
/// This method determines the actual name of the sprite image itself.
/// It ensures that that name doesn't clash with the names of other sprite images.
/// </summary>
/// <param name="fileSystem">
/// This object will be used to write the sprite image to the sprites folder.
/// </param>
/// <param name="spriteImageType">
/// The image type of the sprite, such as Png or Gif.
/// </param>
/// <param name="pixelFormatOverride">
/// If this is not PixelFormat.DontCare or PixelFormat.Undefined, than this pixel format is
/// used for the final sprite image.
///
/// Otherwise, the pixel format is based on the pixel formats of the individual images going
/// into the sprite image - using the pixel format of the image that requires the hightest
/// number of bits per pixel.
/// </param>
/// <param name="paletteAlgorithm">
/// Algorithm to be used when the color depth of the sprite gets changed to
/// an indexed color format (Format1bppIndexed, Format4bppIndexed or Format8bppIndexed).
///
/// This not only happens when pixelFormatOverride is set. The sprite gets constructed using
/// a non-indexed format. It then gets the highest pixel format of all constituing images.
/// That may be an index format.
/// </param>
/// <param name="jpegQuality">
/// If the output format of the sprite is jpg, than this parameter can be used to reduce its quality
/// and therefore its file size.
///
/// This parameter can be between 0 and 100. 100 gives you highest quality. 0 means that the sprite gets saved
/// as is, without changing its quality.
/// </param>
/// <param name="groupUniqueOutputId">
/// Add this string to the file name of the sprite.
/// It encodes the output properties of the group that this sprite belongs to, such as JpegQuality.
/// A sprite with the same constituent images but with different output settings needs to have a different name and url.
/// </param>
/// <returns>
/// Url of the sprite image that was written to disc.
/// </returns>
public string WriteSpriteImage(
IFileSystem fileSystem, ImageType spriteImageType,
PixelFormat pixelFormatOverride, PaletteAlgorithm paletteAlgorithm, int jpegQuality, string groupUniqueOutputId)
{
string urlSpriteImage = null;
// We'll use the combined file paths of all images in the sprite as the basis of the
// the name of the sprite image. This because the file paths identify
// the bitmaps that go into the sprite (the bitmaps are loaded from the image files).
StringBuilder combinedImageFilePaths = new StringBuilder();
// We'll also work out the PixelFormat with the highest number of bits to accommodate all images.
// The lower the PixelFormat we settle on, the smaller the sprite will be in bytes.
PixelFormat highestPixelFormat = PixelFormat.Format1bppIndexed;
// Create a bitmap for the entire sprite
using (Bitmap spriteBitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb))
{
using (Graphics spriteGraphics = Graphics.FromImage(spriteBitmap))
{
// Draw each image in turn onto the sprite
foreach (IMappedImageInfo mappedImageInfo in this.MappedImages)
{
try
{
ImageInfo imageInfo = mappedImageInfo.ImageInfo as ImageInfo;
if (imageInfo == null)
{
throw new Exception("WriteSpriteImage: mappedImageInfo contains null ImageInfo");
}
if (imageInfo.FilePathBroken) { continue; }
highestPixelFormat = PixelFormatUtils.HigherPixelFormat(highestPixelFormat, imageInfo.ImageBitmap.PixelFormat);
// Create Rectangle specifying portion of image to copy to the sprite. Ensure this rectangle
// takes in the entire image.
Rectangle imageRect = new Rectangle(0, 0, imageInfo.Width, imageInfo.Height);
// Create a clone of the image bitmap, and give that clone the same horizontal and vertical resolution
// as the sprite bitmap. This way, the image bitmap will be copied into the sprite pixel by pixel.
// If the resolutions are different, the same number of pixels corresponds to different physical sizes,
// and DrawImage takes physical size into account when drawing an image.
Bitmap clonedImageBitmap = imageInfo.ImageBitmap.Clone(imageRect, spriteBitmap.PixelFormat);
clonedImageBitmap.SetResolution(spriteBitmap.HorizontalResolution, spriteBitmap.VerticalResolution);
// Draw the image onto the sprite
spriteGraphics.DrawImage(
clonedImageBitmap,
mappedImageInfo.X, mappedImageInfo.Y,
imageRect,
GraphicsUnit.Pixel);
// Add the UniqueId of the ImageInfo, consisting of file path of the image and its width and height.
// Need the dimensions, in case you have the image multiple times on different pages,
// resized at different sizes.
combinedImageFilePaths.Append(imageInfo.UniqueId());
// Now that the ImageInfo's bitmap has been used, it can be disposed
imageInfo.DisposeBitmap();
}
catch (FileNotFoundException)
{
// We get here if the image's bitmap could not be read from disk.
// Note that if ExceptionOnMissingFile had been active, this situation would have been
// checked by MapPath, so in that case we would never have gotten here.
}
}
// Create file name of the sprite
string spriteFilename =
groupUniqueOutputId +
UrlUtils.StringHash(combinedImageFilePaths.ToString()) +
UrlUtils.ExtensionForImageType(spriteImageType);
if ((pixelFormatOverride != PixelFormat.DontCare) && (pixelFormatOverride != PixelFormat.Undefined))
{
highestPixelFormat = pixelFormatOverride;
}
// ----------------------------------------
// Create bitmap with optimized pixel format (fewer bits per pixel means that the sprite takes fewer bytes).
// If that fails due to an exception or otherwise, just write the sprite in its current pixel format.
Bitmap colorAdjustedBitmap = null;
try
{
colorAdjustedBitmap =
ImageUtils.ChangePixelFormat(spriteBitmap, highestPixelFormat, paletteAlgorithm);
spriteBitmap.Dispose();
}
catch (Exception)
{
// Just use current bit map
colorAdjustedBitmap = spriteBitmap;
}
urlSpriteImage = fileSystem.WriteImageFile(spriteFilename, colorAdjustedBitmap, spriteImageType, jpegQuality);
}
}
return urlSpriteImage;
}
}
}