/// <summary>The add to analysis log.</summary> /// <param name="imageAssemblyAnalysisLog">The image assembly analysis log.</param> /// <param name="parentAstNode">The parent ast node.</param> /// <param name="image">The image.</param> /// <param name="failureReason">The failure reason.</param> internal static void SafeAdd(this ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, AstNode parentAstNode, string image = null, FailureReason?failureReason = null) { if (imageAssemblyAnalysisLog != null) { imageAssemblyAnalysisLog.Add(new ImageAssemblyAnalysis { AstNode = parentAstNode, Image = image, FailureReason = failureReason }); } }
/// <summary>Assembles the background images</summary> /// <param name="stylesheetNode">the style sheet node</param> /// <param name="dpi">The dpi.</param> /// <param name="threadContext">The thread Context.</param> /// <param name="spritedImageContentItems">The sprited Image Content Items.</param> /// <returns>The stylesheet node with the sprited images aplied.</returns> private AstNode SpriteBackgroundImages(AstNode stylesheetNode, float dpi, IWebGreaseContext threadContext, BlockingCollection <ContentItem> spritedImageContentItems) { // The image assembly is a 3 step process: // 1. Scan the Css followed by Pretty Print // 2. Run the image assembly tool // 3. Update the Css with generated images followed by Pretty Print return(threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.Spriting).Execute(() => { // Execute the pipeline for image assembly scan var scanLog = this.ExecuteImageAssemblyScan(stylesheetNode, threadContext); // Execute the pipeline for image assembly tool var imageMaps = new List <ImageLog>(); var count = 0; foreach (var scanOutput in scanLog.ImageAssemblyScanOutputs) { var spriteResult = this.SpriteImageFromLog(scanOutput, this.ImageAssembleScanDestinationFile + (count == 0 ? string.Empty : "." + count) + ".xml", scanLog.ImageAssemblyAnalysisLog, threadContext, spritedImageContentItems); if (spriteResult != null) { imageMaps.Add(spriteResult); count++; } } // Step # 8 - Execute the pipeline for image assembly update stylesheetNode = this.ExecuteImageAssemblyUpdate(stylesheetNode, imageMaps, dpi); if (!string.IsNullOrWhiteSpace(this.ImageSpritingLogPath)) { scanLog.ImageAssemblyAnalysisLog.Save(this.ImageSpritingLogPath); } var imageAssemblyAnalysisLog = scanLog.ImageAssemblyAnalysisLog; if (this.ErrorOnInvalidSprite && imageAssemblyAnalysisLog.FailedSprites.Any()) { foreach (var failedSprite in imageAssemblyAnalysisLog.FailedSprites) { var failureMessage = ImageAssemblyAnalysisLog.GetFailureMessage(failedSprite); if (!string.IsNullOrWhiteSpace(failedSprite.Image)) { threadContext.Log.Error( "Failed to sprite image {0}\r\nReason:{1}\r\nCss:{2}".InvariantFormat( failedSprite.Image, failureMessage, failedSprite.AstNode.PrettyPrint())); } else { threadContext.Log.Error( "Failed to sprite:{0}\r\nReason:{1}".InvariantFormat( failedSprite.Image, failureMessage)); } } } return stylesheetNode; })); }
/// <summary>The create sprited images.</summary> /// <param name="mapXmlFile">The map xml file.</param> /// <param name="imageAssemblyAnalysisLog">The image assembly analysis log.</param> /// <param name="imageReferencesToAssemble">The image references to assemble.</param> /// <param name="cacheSection">The cache section.</param> /// <param name="results">The results.</param> /// <param name="threadContext">The thread Context.</param> /// <returns>The <see cref="bool"/>.</returns> private ImageLog CreateSpritedImages( string mapXmlFile, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, IEnumerable <InputImage> imageReferencesToAssemble, ICacheSection cacheSection, BlockingCollection <ContentItem> results, IWebGreaseContext threadContext) { if (!Directory.Exists(this.ImagesOutputDirectory)) { Directory.CreateDirectory(this.ImagesOutputDirectory); } var imageMap = ImageAssembleGenerator.AssembleImages( imageReferencesToAssemble.ToSafeReadOnlyCollection(), SpritePackingType.Vertical, this.ImagesOutputDirectory, string.Empty, true, threadContext, this.ImageAssemblyPadding, imageAssemblyAnalysisLog, this.ForcedSpritingImageType); if (imageMap == null || imageMap.Document == null) { return(null); } var destinationDirectory = threadContext.Configuration.DestinationDirectory; if (!string.IsNullOrWhiteSpace(this.ImageAssembleScanDestinationFile)) { var scanXml = imageMap.Document.ToString(); FileHelper.WriteFile(mapXmlFile, scanXml); cacheSection.AddResult(ContentItem.FromFile(mapXmlFile), CacheFileCategories.SpriteLogFileXml); } var imageLog = new ImageLog(imageMap.Document); cacheSection.AddResult(ContentItem.FromContent(imageLog.ToJson(true)), CacheFileCategories.SpriteLogFile); foreach (var spritedFile in imageLog.InputImages.Select(il => il.OutputFilePath).Distinct()) { var spritedImageContentItem = ContentItem.FromFile(spritedFile, spritedFile.MakeRelativeToDirectory(destinationDirectory)); results.Add(spritedImageContentItem); cacheSection.AddResult(spritedImageContentItem, CacheFileCategories.HashedSpriteImage); } return(imageLog); }
/// <summary>The sprite image.</summary> /// <param name="scanOutput">The scan Output.</param> /// <param name="mapXmlFile">The map xml file</param> /// <param name="imageAssemblyAnalysisLog">The image Assembly Analysis Log.</param> /// <param name="threadContext">The thread Context.</param> /// <param name="spritedImageContentItems">The sprited Image Content Items.</param> /// <returns>The <see cref="ImageLog"/>.</returns> private ImageLog SpriteImageFromLog(ImageAssemblyScanOutput scanOutput, string mapXmlFile, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, IWebGreaseContext threadContext, BlockingCollection <ContentItem> spritedImageContentItems) { if (scanOutput == null || !scanOutput.ImageReferencesToAssemble.Any()) { return(null); } ImageLog imageLog = null; var imageReferencesToAssemble = scanOutput.ImageReferencesToAssemble; if (imageReferencesToAssemble == null || imageReferencesToAssemble.Count == 0) { return(null); } var varBySettings = new { imageMap = GetRelativeSpriteCacheKey(imageReferencesToAssemble, threadContext), this.ImageAssemblyPadding }; var success = threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.Spriting, SectionIdParts.Assembly) .MakeCachable(varBySettings, false, true) .RestoreFromCacheAction(cacheSection => { imageLog = RestoreSpritedImagesFromCache(mapXmlFile, cacheSection, spritedImageContentItems, threadContext.Configuration.DestinationDirectory, this.ImageAssembleScanDestinationFile); return(imageLog != null); }) .Execute(cacheSection => { imageLog = this.CreateSpritedImages(mapXmlFile, imageAssemblyAnalysisLog, imageReferencesToAssemble, cacheSection, spritedImageContentItems, threadContext); return(imageLog != null); }); return (success ? imageLog : null); }
/// <summary>Verify that url has some value</summary> /// <param name="parent">The parent AST node</param> /// <param name="imageReferencesToIgnore">The image reference to ignore</param> /// <param name="imageAssemblyAnalysisLog">The logging object</param> /// <param name="shouldIgnore">The result of scan if we should ignore the image reference</param> /// <returns>True if px units are used</returns> internal bool VerifyBackgroundUrl(AstNode parent, HashSet <string> imageReferencesToIgnore, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, out bool shouldIgnore) { shouldIgnore = false; if (string.IsNullOrWhiteSpace(this.Url)) { imageAssemblyAnalysisLog.SafeAdd(parent, this.Url, FailureReason.NoUrl); return(false); } if (imageReferencesToIgnore != null) { var url = this.Url; var fullImageUrl = url.NormalizeUrl(); if (imageReferencesToIgnore.Contains(fullImageUrl)) { // Log diagnostics imageAssemblyAnalysisLog.SafeAdd(parent, this.Url, FailureReason.IgnoreUrl); shouldIgnore = true; return(false); } } return(true); }
internal static bool TryGetBackgroundDeclaration(this IEnumerable <DeclarationNode> declarationAstNodes, AstNode parentAstNode, out Background backgroundNode, out BackgroundImage backgroundImageNode, out BackgroundPosition backgroundPositionNode, out DeclarationNode backgroundSize, List <string> imageReferencesInInvalidDeclarations, HashSet <string> imageReferencesToIgnore, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, string outputUnit, double outputUnitFactor, bool ignoreImagesWithNonDefaultBackgroundSize = false) { // Initialize the nodes to null backgroundNode = null; backgroundImageNode = null; backgroundPositionNode = null; backgroundSize = null; // With CSS3 multiple urls can be present in a single rule, this is not yet supported // background: url(flower.png), url(ball.png), url(grass.png) no-repeat; if (BackgroundImage.HasMultipleUrls(parentAstNode.MinifyPrint())) { imageAssemblyAnalysisLog.SafeAdd(parentAstNode, null, FailureReason.MultipleUrls); return(false); } // This will check if the selector hash -wg-spriting: ignore. If it does we will ignore the sprite. var webGreaseSpritingProperty = declarationAstNodes.FirstOrDefault(d => d.Property == "-wg-spriting"); if (webGreaseSpritingProperty != null && webGreaseSpritingProperty.ExprNode.TermNode.StringBasedValue == "ignore") { imageAssemblyAnalysisLog.SafeAdd(parentAstNode, null, FailureReason.SpritingIgnore); return(false); } // The list of declarations should not have the duplicate declaration // properties. Validate and get the dictionary of declarations. var declarationProperties = declarationAstNodes.LoadDeclarationPropertiesDictionary(); // The selector design is inefficient if the shorthand notation and long name is defined // in a scope of ruleset declaration, media ruleset declaration or page declaration. This // would be a great feature to add to Optimization visitor but would require a full table // scanning which is not yet implemented. // Per MSN CSS standards, the practice of defining the shorthand notation and long name // in scope of same selector is not allowed. DeclarationNode declarationAstNode; if (declarationProperties.TryGetValue(ImageAssembleConstants.Background, out declarationAstNode)) { // There should not be any short and long notation simultaneosuly used in these set of declarations. // For example: Such a list of declarations end up in inefficient CSS. // #selector // { // background:url(foo.gif); // background-image:url(../../i/D5/DF5D9B4EFD5CFF9122942A67A1EEC5.gif); // background-position:500px 500px; // background-repeat:no-repeat // } // By design, we are not computing cascade here. // TODO: RTUIT: Add support to override some of these with extra values depending on the order, or use optimization to do this when merging styles. if (declarationProperties.ContainsKey(ImageAssembleConstants.BackgroundRepeat) || declarationProperties.ContainsKey(ImageAssembleConstants.BackgroundImage) || declarationProperties.ContainsKey(ImageAssembleConstants.BackgroundPosition)) { throw new ImageAssembleException(CssStrings.DuplicateBackgroundFormatError); } // Load the model for the "background" declaration var parsedBackground = new Background(declarationAstNode, outputUnit, outputUnitFactor); //// //// The url should be present //// bool shouldIgnore; if (!parsedBackground.BackgroundImage.VerifyBackgroundUrl(parentAstNode, imageReferencesToIgnore, imageAssemblyAnalysisLog, out shouldIgnore) || shouldIgnore) { return(false); } //// //// The "no-repeat" term should be explicitly configured on the "background" declaration //// if (!parsedBackground.BackgroundRepeat.VerifyBackgroundNoRepeat()) { imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackground.Url, FailureReason.BackgroundRepeatInvalid); UpdateFailedUrlsList(parsedBackground.Url, imageReferencesInInvalidDeclarations); return(false); } //// //// The background position should only be empty, x = any value and y = 0, top or px //// if (!parsedBackground.BackgroundPosition.IsVerticalSpriteCandidate()) { imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackground.Url, FailureReason.IncorrectPosition); UpdateFailedUrlsList(parsedBackground.Url, imageReferencesInInvalidDeclarations); return(false); } //// Try to get the background size, returns false if we want to ignore images with background sizes and the background size is set to a non-default value. if (!TryGetBackgroundSize(ignoreImagesWithNonDefaultBackgroundSize, declarationProperties, out backgroundSize)) { imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackground.Url, FailureReason.BackgroundSizeIsSetToNonDefaultValue); UpdateFailedUrlsList(parsedBackground.Url, imageReferencesInInvalidDeclarations); return(false); } backgroundNode = parsedBackground; imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackground.Url); //// SUCCESS - This is the candidate! return(true); } // Now there should be declaration for "background-image" or "background-repeat" or both if (declarationProperties.TryGetValue(ImageAssembleConstants.BackgroundImage, out declarationAstNode)) { // Load the property model for the "background-image" declaration var parsedBackgroundImage = new BackgroundImage(declarationAstNode); //// //// The url should be present //// bool shouldIgnore; if (!parsedBackgroundImage.VerifyBackgroundUrl(parentAstNode, imageReferencesToIgnore, imageAssemblyAnalysisLog, out shouldIgnore) || shouldIgnore) { return(false); } //// //// There is a "background-repeat" declaration found //// DeclarationNode backgroundRepeat; if (!declarationProperties.TryGetValue(ImageAssembleConstants.BackgroundRepeat, out backgroundRepeat)) { imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url, FailureReason.NoRepeat); UpdateFailedUrlsList(parsedBackgroundImage.Url, imageReferencesInInvalidDeclarations); return(false); } //// //// Now make sure that "background-repeat" is "no-repeat" //// if (!new BackgroundRepeat(backgroundRepeat).VerifyBackgroundNoRepeat()) { imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url, FailureReason.BackgroundRepeatInvalid); UpdateFailedUrlsList(parsedBackgroundImage.Url, imageReferencesInInvalidDeclarations); return(false); } //// Try to get the background size, returns false if we want to ignore images with background sizes and the background size is set to a non-default value. if (!TryGetBackgroundSize(ignoreImagesWithNonDefaultBackgroundSize, declarationProperties, out backgroundSize)) { imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url, FailureReason.BackgroundSizeIsSetToNonDefaultValue); UpdateFailedUrlsList(parsedBackgroundImage.Url, imageReferencesInInvalidDeclarations); return(false); } //// //// The background position should only be empty, x = any value and y = 0, top or px //// DeclarationNode backgroundPosition; if (declarationProperties.TryGetValue(ImageAssembleConstants.BackgroundPosition, out backgroundPosition)) { // Now if there is a "background-position" declaration (optional), lets make // it should only be empty, px or left/top or right/px var parsedBackgroundPosition = new BackgroundPosition(backgroundPosition, outputUnit, outputUnitFactor); if (!parsedBackgroundPosition.IsVerticalSpriteCandidate()) { imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url, FailureReason.IncorrectPosition); UpdateFailedUrlsList(parsedBackgroundImage.Url, imageReferencesInInvalidDeclarations); return(false); } backgroundImageNode = parsedBackgroundImage; backgroundPositionNode = parsedBackgroundPosition; imageAssemblyAnalysisLog.SafeAdd(parentAstNode, parsedBackgroundImage.Url); //// SUCCESS - This is the candidate! return(true); } backgroundImageNode = parsedBackgroundImage; imageAssemblyAnalysisLog.SafeAdd(parentAstNode, backgroundImageNode.Url); //// SUCCESS - This is the candidate! return(true); } return(false); }
internal static ImageMap AssembleImages(ReadOnlyCollection <InputImage> inputImages, SpritePackingType packingType, string assembleFileFolder, string mapFileName, string pngOptimizerToolCommand, bool dedup, IWebGreaseContext context, int?imagePadding = null, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog = null, ImageType?forcedImageType = null) { // deduping is optional. CssPipelineTask already has deduping built into it, so it skips deduping in ImageAssembleTool. var inputImagesDeduped = dedup ? DedupImages(inputImages, context) : inputImages; var xmlMap = new ImageMap(mapFileName); Safe.LockFiles( inputImages.Select(ii => new FileInfo(ii.AbsoluteImagePath)).ToArray(), () => { var separatedLists = SeparateByImageType(inputImagesDeduped, forcedImageType); #if DEBUG foreach (ImageType imageType in Enum.GetValues(typeof(ImageType))) { Console.WriteLine(); Console.WriteLine(Enum.GetName(typeof(ImageType), imageType)); foreach (var entry in separatedLists[imageType]) { Console.WriteLine(entry.InputImage.OriginalImagePath); } } #endif var padding = imagePadding ?? DefaultPadding; var registeredAssemblers = RegisterAvailableAssemblers(context); List <BitmapContainer> separatedList = null; foreach (var registeredAssembler in registeredAssemblers) { var assembled = false; try { // Set Image orientation as passed registeredAssembler.PackingType = packingType; // Set Xml Image Xml Map registeredAssembler.ImageXmlMap = xmlMap; xmlMap.AppendPadding(padding.ToString(CultureInfo.InvariantCulture)); registeredAssembler.PaddingBetweenImages = padding; // Set PNG Optimizer tool path for PngAssemble registeredAssembler.OptimizerToolCommand = pngOptimizerToolCommand; // Assemble images of this type separatedList = separatedLists[registeredAssembler.Type]; if (separatedList.Any()) { // Set Assembled Image Name registeredAssembler.AssembleFileName = GenerateAssembleFileName(separatedList.Select(s => s.InputImage), assembleFileFolder) + registeredAssembler.DefaultExtension; assembled = registeredAssembler.Assemble(separatedList); } } finally { if (assembled) { foreach (var entry in separatedList) { if (entry.Bitmap != null) { if (imageAssemblyAnalysisLog != null) { imageAssemblyAnalysisLog.UpdateSpritedImage(registeredAssembler.Type, entry.InputImage.OriginalImagePath, registeredAssembler.AssembleFileName); } context.Cache.CurrentCacheSection.AddSourceDependency(entry.InputImage.AbsoluteImagePath); entry.Bitmap.Dispose(); } } } } } var notSupportedList = separatedLists[ImageType.NotSupported]; if (notSupportedList != null && notSupportedList.Count > 0) { var message = new StringBuilder("The following files were not assembled because their formats are not supported:"); foreach (var entry in notSupportedList) { message.Append(" " + entry.InputImage.OriginalImagePath); } #if DEBUG Console.WriteLine(message.ToString()); #endif throw new ImageAssembleException(message.ToString()); } }); return(xmlMap); }
/// <summary>Invokes Assemble method of appropriate Image Assembler depending upon /// the type of images to be Assembled.</summary> /// <param name="inputImages">List of InputImage</param> /// <param name="packingType">Image Packing Type(Horizontal/Vertical)</param> /// <param name="assembleFileFolder">folder path where the assembled file will be created.</param> /// <param name="pngOptimizerToolCommand">PNG Optimizer tool command</param> /// <param name="dedup">Remove duplicate images</param> /// <param name="context">The webgrease context</param> /// <param name="imagePadding">The image padding</param> /// <param name="imageAssemblyAnalysisLog">The image Assembly Analysis Log.</param> /// <param name="forcedImageType">The forced image type to override detection.</param> /// <returns>The <see cref="ImageMap"/>.</returns> internal static ImageMap AssembleImages(ReadOnlyCollection <InputImage> inputImages, SpritePackingType packingType, string assembleFileFolder, string pngOptimizerToolCommand, bool dedup, IWebGreaseContext context, int?imagePadding = null, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog = null, ImageType?forcedImageType = null) { return(AssembleImages(inputImages, packingType, assembleFileFolder, null, pngOptimizerToolCommand, dedup, context, imagePadding, imageAssemblyAnalysisLog, forcedImageType)); }